/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
 * Copyright 2020 Joyent, Inc.
 */

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <libintl.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <priv.h>

#ifdef	__dilos__
#include "solaris-ldap.h"
#include "solaris-lber.h"
#endif	/* __dilos__ */

#include "ns_sldap.h"
#include "ns_internal.h"
#include "ns_cache_door.h"
#include "ns_connmgmt.h"

#define	_NIS_FILTER	"nisdomain=*"
#define	_NIS_DOMAIN	"nisdomain"
static const char *nis_domain_attrs[] = {
	_NIS_DOMAIN,
	(char *)NULL
};

static int validate_filter(ns_ldap_cookie_t *cookie);

void
__ns_ldap_freeEntry(ns_ldap_entry_t *ep)
{
	int		j, k = 0;

	if (ep == NULL)
		return;

	if (ep->attr_pair == NULL) {
		free(ep);
		return;
	}
	for (j = 0; j < ep->attr_count; j++) {
		if (ep->attr_pair[j] == NULL)
			continue;
		if (ep->attr_pair[j]->attrname)
			free(ep->attr_pair[j]->attrname);
		if (ep->attr_pair[j]->attrvalue) {
			for (k = 0; (k < ep->attr_pair[j]->value_count) &&
			    (ep->attr_pair[j]->attrvalue[k]); k++) {
				free(ep->attr_pair[j]->attrvalue[k]);
			}
			free(ep->attr_pair[j]->attrvalue);
		}
		free(ep->attr_pair[j]);
	}
	free(ep->attr_pair);
	free(ep);
}

static void
_freeControlList(LDAPControl ***ctrls)
{
	LDAPControl	**ctrl;

	if (ctrls == NULL || *ctrls == NULL)
		return;

	for (ctrl = *ctrls; *ctrl != NULL; ctrl++)
		ldap_control_free(*ctrl);
	free(*ctrls);
	*ctrls = NULL;
}
/*
 * Convert attribute type in a RDN that has an attribute mapping to the
 * original mappped type.
 * e.g.
 * cn<->cn-st and iphostnumber<->iphostnumber-st
 * cn-st=aaa+iphostnumber-st=10.10.01.01
 * is mapped to
 * cn=aaa+iphostnumber=10.10.01.01
 *
 * Input - service: e.g. hosts, passwd etc.
 *         rdn: RDN
 * Return: NULL - No attribute mapping in the RDN
 *         Non-NULL - The attribute type(s) in the RDN are mapped and
 *                    the memory is allocated for the new rdn.
 *
 */
static char *
_cvtRDN(const char *service, const char *rdn)
{
	char	**attrs, **mapped_attrs, **mapp, *type, *value, *attr;
	char	*new_rdn = NULL;
	int	nAttr = 0, i, attr_mapped, len = 0;

	/* Break down "type=value\0" pairs. Assume RDN is normalized */
	if ((attrs = ldap_explode_rdn(rdn, 0)) == NULL)
		return (NULL);

	for (nAttr = 0; attrs[nAttr] != NULL; nAttr++)
		;

	if ((mapped_attrs = (char **)calloc(nAttr, sizeof (char *))) == NULL) {
		ldap_value_free(attrs);
		return (NULL);
	}

	attr_mapped = 0;
	for (i = 0; i < nAttr; i++) {
		/* Parse type=value pair */
		if ((type = strtok_r(attrs[i], "=", &value)) == NULL ||
		    value == NULL)
			goto cleanup;
		/* Reverse map: e.g. cn-sm -> cn */
		mapp = __ns_ldap_getOrigAttribute(service, type);
		if (mapp != NULL && mapp[0] != NULL) {
			/* The attribute mapping is found */
			type = mapp[0];
			attr_mapped = 1;

			/* "type=value\0" */
			len = strlen(type) + strlen(value) + 2;

			/* Reconstruct type=value pair. A string is allocated */
			if ((attr = (char *)calloc(1, len)) == NULL) {
				__s_api_free2dArray(mapp);
				goto cleanup;
			}
			(void) snprintf(attr, len, "%s=%s", type, value);
			mapped_attrs[i] = attr;
		} else {
			/*
			 * No attribute mapping. attrs[i] is going to be copied
			 * later. Restore "type\0value\0" back to
			 * "type=value\0".
			 */
			type[strlen(type)] = '=';
		}
		__s_api_free2dArray(mapp);
	}
	if (attr_mapped == 0)
		/* No attribute mapping. Don't bother to reconstruct RDN */
		goto cleanup;

	len = 0;
	/* Reconstruct RDN from type=value pairs */
	for (i = 0; i < nAttr; i++) {
		if (mapped_attrs[i])
			len += strlen(mapped_attrs[i]);
		else
			len += strlen(attrs[i]);
		/* Add 1 for "+" */
		len++;
	}
	if ((new_rdn = (char *)calloc(1, ++len)) == NULL)
		goto cleanup;
	for (i = 0; i < nAttr; i++) {
		if (i > 0)
			/* Add seperator */
			(void) strlcat(new_rdn, "+", len);

		if (mapped_attrs[i])
			(void) strlcat(new_rdn, mapped_attrs[i], len);
		else
			(void) strlcat(new_rdn, attrs[i], len);

	}
cleanup:
	ldap_value_free(attrs);
	if (mapped_attrs) {
		if (attr_mapped) {
			for (i = 0; i < nAttr; i++) {
				if (mapped_attrs[i])
					free(mapped_attrs[i]);
			}
		}
		free(mapped_attrs);
	}

	return (new_rdn);
}
/*
 * Convert attribute type in a DN that has an attribute mapping to the
 * original mappped type.
 * e.g
 * The mappings are cn<->cn-sm, iphostnumber<->iphostnumber-sm
 *
 * dn: cn-sm=aaa+iphostnumber-sm=9.9.9.9,dc=central,dc=sun,dc=com
 * is converted to
 * dn: cn=aaa+iphostnumber=9.9.9.9,dc=central,dc=sun,dc=com
 *
 * Input - service: e.g. hosts, passwd etc.
 *         dn: the value of a distinguished name
 * Return - NULL: error
 *          non-NULL: A converted DN and the memory is allocated
 */
static char *
_cvtDN(const char *service, const char *dn)
{
	char	**mapped_rdns;
	char	**rdns, *new_rdn, *new_dn = NULL;
	int	nRdn = 0, i, len = 0, rdn_mapped;

	if (service == NULL || dn == NULL)
		return (NULL);

	if ((rdns = ldap_explode_dn(dn, 0)) == NULL)
		return (NULL);

	for (nRdn = 0; rdns[nRdn] != NULL; nRdn++)
		;

	if ((mapped_rdns = (char **)calloc(nRdn, sizeof (char *))) == NULL) {
		ldap_value_free(rdns);
		return (NULL);
	}

	rdn_mapped = 0;
	/* Break down RDNs in a DN */
	for (i = 0; i < nRdn; i++) {
		if ((new_rdn = _cvtRDN(service, rdns[i])) != NULL) {
			mapped_rdns[i] = new_rdn;
			rdn_mapped = 1;
		}
	}
	if (rdn_mapped == 0) {
		/*
		 * No RDN contains any attribute mapping.
		 * Don't bother to reconstruct DN from RDN. Copy DN directly.
		 */
		new_dn = strdup(dn);
		goto cleanup;
	}
	/*
	 * Reconstruct dn from RDNs.
	 * Calculate the length first.
	 */
	for (i = 0; i < nRdn; i++) {
		if (mapped_rdns[i])
			len += strlen(mapped_rdns[i]);
		else
			len += strlen(rdns[i]);

		/* add 1 for ',' */
		len ++;
	}
	if ((new_dn = (char *)calloc(1, ++len)) == NULL)
		goto cleanup;
	for (i = 0; i < nRdn; i++) {
		if (i > 0)
			/* Add seperator */
			(void) strlcat(new_dn, ",", len);

		if (mapped_rdns[i])
			(void) strlcat(new_dn, mapped_rdns[i], len);
		else
			(void) strlcat(new_dn, rdns[i], len);

	}

cleanup:
	ldap_value_free(rdns);
	if (mapped_rdns) {
		if (rdn_mapped) {
			for (i = 0; i < nRdn; i++) {
				if (mapped_rdns[i])
					free(mapped_rdns[i]);
			}
		}
		free(mapped_rdns);
	}

	return (new_dn);
}
/*
 * Convert a single ldap entry from a LDAPMessage
 * into an ns_ldap_entry structure.
 * Schema map the entry if specified in flags
 */

static int
__s_api_cvtEntry(LDAP *ld, const char *service, LDAPMessage *e, int flags,
    ns_ldap_entry_t **ret, ns_ldap_error_t **error)
{

	ns_ldap_entry_t	*ep = NULL;
	ns_ldap_attr_t	**ap = NULL;
	BerElement	*ber;
	char		*attr = NULL;
	char		**vals = NULL;
	char		**mapping;
	char		*dn;
	int		nAttrs = 0;
	int		i, j, k = 0;
	char		**gecos_mapping = NULL;
	int		gecos_val_index[3] = { -1, -1, -1};
	char		errstr[MAXERROR];
	int		schema_mapping_existed = FALSE;
	int		gecos_mapping_existed = FALSE;
	int		gecos_attr_matched;
	int		auto_service = FALSE;
	int		rc = NS_LDAP_SUCCESS;

	if (e == NULL || ret == NULL || error == NULL)
		return (NS_LDAP_INVALID_PARAM);

	*error = NULL;

	ep = (ns_ldap_entry_t *)calloc(1, sizeof (ns_ldap_entry_t));
	if (ep == NULL)
		return (NS_LDAP_MEMORY);

	if (service != NULL &&
	    (strncasecmp(service, "auto_", 5) == 0 ||
	    strcasecmp(service, "automount") == 0))
		auto_service = TRUE;
	/*
	 * see if schema mapping existed for the given service
	 */
	mapping = __ns_ldap_getOrigAttribute(service,
	    NS_HASH_SCHEMA_MAPPING_EXISTED);
	if (mapping) {
		schema_mapping_existed = TRUE;
		__s_api_free2dArray(mapping);
		mapping = NULL;
	} else if (auto_service) {
		/*
		 * If service == auto_* and no
		 * schema mapping found
		 * then try automount
		 * There is certain case that schema mapping exist
		 * but __ns_ldap_getOrigAttribute(service,
		 *	NS_HASH_SCHEMA_MAPPING_EXISTED);
		 * returns NULL.
		 * e.g.
		 * NS_LDAP_ATTRIBUTEMAP = automount:automountMapName=AAA
		 * NS_LDAP_OBJECTCLASSMAP = automount:automountMap=MynisMap
		 * NS_LDAP_OBJECTCLASSMAP = automount:automount=MynisObject
		 *
		 * Make a check for schema_mapping_existed here
		 * so later on __s_api_convert_automountmapname won't be called
		 * unnecessarily. It is also used for attribute mapping
		 * and objectclass mapping.
		 */
		mapping = __ns_ldap_getOrigAttribute("automount",
		    NS_HASH_SCHEMA_MAPPING_EXISTED);
		if (mapping) {
			schema_mapping_existed = TRUE;
			__s_api_free2dArray(mapping);
			mapping = NULL;
		}
	}

	nAttrs = 1;  /* start with 1 for the DN attr */
	for (attr = ldap_first_attribute(ld, e, &ber); attr != NULL;
	    attr = ldap_next_attribute(ld, e, ber)) {
		nAttrs++;
		ldap_memfree(attr);
		attr = NULL;
	}
	ber_free(ber, 0);
	ber = NULL;

	ep->attr_count = nAttrs;

	/*
	 * add 1 for "gecos" 1 to N attribute mapping,
	 * just in case it is needed.
	 * ep->attr_count will be updated later if that is true.
	 */
	ap = (ns_ldap_attr_t **)calloc(ep->attr_count + 1,
	    sizeof (ns_ldap_attr_t *));
	if (ap == NULL) {
		__ns_ldap_freeEntry(ep);
		ep = NULL;
		return (NS_LDAP_MEMORY);
	}
	ep->attr_pair = ap;

	/* DN attribute */
	dn = ldap_get_dn(ld, e);
	ap[0] = (ns_ldap_attr_t *)calloc(1, sizeof (ns_ldap_attr_t));
	if (ap[0] == NULL) {
		ldap_memfree(dn);
		dn = NULL;
		__ns_ldap_freeEntry(ep);
		ep = NULL;
		return (NS_LDAP_MEMORY);
	}

	if ((ap[0]->attrname = strdup("dn")) == NULL) {
		ldap_memfree(dn);
		dn = NULL;
		__ns_ldap_freeEntry(ep);
		ep = NULL;
		return (NS_LDAP_INVALID_PARAM);
	}
	ap[0]->value_count = 1;
	if ((ap[0]->attrvalue = (char **)
	    calloc(2, sizeof (char *))) == NULL) {
		ldap_memfree(dn);
		dn = NULL;
		__ns_ldap_freeEntry(ep);
		ep = NULL;
		return (NS_LDAP_MEMORY);
	}

	if (schema_mapping_existed && ((flags & NS_LDAP_NOT_CVT_DN) == 0))
		ap[0]->attrvalue[0] = _cvtDN(service, dn);
	else
		ap[0]->attrvalue[0] = strdup(dn);

	if (ap[0]->attrvalue[0] == NULL) {
		ldap_memfree(dn);
		dn = NULL;
		__ns_ldap_freeEntry(ep);
		ep = NULL;
		return (NS_LDAP_MEMORY);
	}
	ldap_memfree(dn);
	dn = NULL;

	if ((flags & NS_LDAP_NOMAP) == 0 && auto_service &&
	    schema_mapping_existed) {
		rc = __s_api_convert_automountmapname(service,
		    &ap[0]->attrvalue[0],
		    error);
		if (rc != NS_LDAP_SUCCESS) {
			__ns_ldap_freeEntry(ep);
			ep = NULL;
			return (rc);
		}
	}

	/* other attributes */
	for (attr = ldap_first_attribute(ld, e, &ber), j = 1;
	    attr != NULL && j != nAttrs;
	    attr = ldap_next_attribute(ld, e, ber), j++) {
		/* allocate new attr name */

		if ((ap[j] = (ns_ldap_attr_t *)
		    calloc(1, sizeof (ns_ldap_attr_t))) == NULL) {
			ber_free(ber, 0);
			ber = NULL;
			__ns_ldap_freeEntry(ep);
			ep = NULL;
			if (gecos_mapping)
				__s_api_free2dArray(gecos_mapping);
			gecos_mapping = NULL;
			return (NS_LDAP_MEMORY);
		}

		if ((flags & NS_LDAP_NOMAP) || schema_mapping_existed == FALSE)
			mapping = NULL;
		else
			mapping = __ns_ldap_getOrigAttribute(service, attr);

		if (mapping == NULL && auto_service &&
		    schema_mapping_existed && (flags & NS_LDAP_NOMAP) == 0)
			/*
			 * if service == auto_* and no schema mapping found
			 * and schema_mapping_existed is TRUE and NS_LDAP_NOMAP
			 * is not set then try automount e.g.
			 * NS_LDAP_ATTRIBUTEMAP = automount:automountMapName=AAA
			 */
			mapping = __ns_ldap_getOrigAttribute("automount",
			    attr);

		if (mapping == NULL) {
			if ((ap[j]->attrname = strdup(attr)) == NULL) {
				ber_free(ber, 0);
				ber = NULL;
				__ns_ldap_freeEntry(ep);
				ep = NULL;
				if (gecos_mapping)
					__s_api_free2dArray(gecos_mapping);
				gecos_mapping = NULL;
				return (NS_LDAP_MEMORY);
			}
		} else {
			/*
			 * for "gecos" 1 to N mapping,
			 * do not remove the mapped attribute,
			 * just create a new gecos attribute
			 * and append it to the end of the attribute list
			 */
			if (strcasecmp(mapping[0], "gecos") == 0) {
				ap[j]->attrname = strdup(attr);
				gecos_mapping_existed = TRUE;
			} else {
				ap[j]->attrname = strdup(mapping[0]);
			}

			if (ap[j]->attrname == NULL) {
				ber_free(ber, 0);
				ber = NULL;
				__ns_ldap_freeEntry(ep);
				ep = NULL;
				if (gecos_mapping)
					__s_api_free2dArray(gecos_mapping);
				gecos_mapping = NULL;
				return (NS_LDAP_MEMORY);
			}
			/*
			 * 1 to N attribute mapping processing
			 * is only done for "gecos"
			 */

			if (strcasecmp(mapping[0], "gecos") == 0) {
				/*
				 * get attribute mapping for "gecos",
				 * need to know the number and order of the
				 * mapped attributes
				 */
				if (gecos_mapping == NULL) {
					gecos_mapping =
					    __ns_ldap_getMappedAttributes(
					    service, mapping[0]);
					if (gecos_mapping == NULL ||
					    gecos_mapping[0] == NULL) {
						/*
						 * this should never happens,
						 * syslog the error
						 */
						(void) sprintf(errstr,
						    gettext(
						    "Attribute mapping "
						    "inconsistency "
						    "found for attributes "
						    "'%s' and '%s'."),
						    mapping[0], attr);
						syslog(LOG_ERR, "libsldap: %s",
						    errstr);

						ber_free(ber, 0);
						ber = NULL;
						__ns_ldap_freeEntry(ep);
						ep = NULL;
						__s_api_free2dArray(mapping);
						mapping = NULL;
						if (gecos_mapping)
							__s_api_free2dArray(
							    gecos_mapping);
						gecos_mapping = NULL;
						return (NS_LDAP_INTERNAL);
					}
				}

				/*
				 * is this attribute the 1st, 2nd, or
				 * 3rd attr in the mapping list?
				 */
				gecos_attr_matched = FALSE;
				for (i = 0; i < 3 && gecos_mapping[i]; i++) {
					if (gecos_mapping[i] &&
					    strcasecmp(gecos_mapping[i],
					    attr) == 0) {
						gecos_val_index[i] = j;
						gecos_attr_matched = TRUE;
						break;
					}
				}
				if (gecos_attr_matched == FALSE) {
					/*
					 * Not match found.
					 * This should never happens,
					 * syslog the error
					 */
					(void) sprintf(errstr,
					    gettext(
					    "Attribute mapping "
					    "inconsistency "
					    "found for attributes "
					    "'%s' and '%s'."),
					    mapping[0], attr);
					syslog(LOG_ERR, "libsldap: %s", errstr);

					ber_free(ber, 0);
					ber = NULL;
					__ns_ldap_freeEntry(ep);
					ep = NULL;
					__s_api_free2dArray(mapping);
					mapping = NULL;
					__s_api_free2dArray(gecos_mapping);
					gecos_mapping = NULL;
					return (NS_LDAP_INTERNAL);
				}
			}
			__s_api_free2dArray(mapping);
			mapping = NULL;
		}

		if ((vals = ldap_get_values(ld, e, attr)) != NULL) {

			if ((ap[j]->value_count =
			    ldap_count_values(vals)) == 0) {
				ldap_value_free(vals);
				vals = NULL;
				continue;
			} else {
				ap[j]->attrvalue = (char **)
				    calloc(ap[j]->value_count+1,
				    sizeof (char *));
				if (ap[j]->attrvalue == NULL) {
					ber_free(ber, 0);
					ber = NULL;
					__ns_ldap_freeEntry(ep);
					ep = NULL;
					if (gecos_mapping)
						__s_api_free2dArray(
						    gecos_mapping);
					gecos_mapping = NULL;
					return (NS_LDAP_MEMORY);
				}
			}

			/* map object classes if necessary */
			if ((flags & NS_LDAP_NOMAP) == 0 &&
			    schema_mapping_existed && ap[j]->attrname &&
			    strcasecmp(ap[j]->attrname, "objectclass") == 0) {
				for (k = 0; k < ap[j]->value_count; k++) {
					mapping =
					    __ns_ldap_getOrigObjectClass(
					    service, vals[k]);

					if (mapping == NULL && auto_service)
						/*
						 * if service == auto_* and no
						 * schema mapping found
						 * then try automount
						 */
					mapping =
					    __ns_ldap_getOrigObjectClass(
					    "automount", vals[k]);

					if (mapping == NULL) {
						ap[j]->attrvalue[k] =
						    strdup(vals[k]);
					} else {
						ap[j]->attrvalue[k] =
						    strdup(mapping[0]);
						__s_api_free2dArray(mapping);
						mapping = NULL;
					}
					if (ap[j]->attrvalue[k] == NULL) {
						ber_free(ber, 0);
						ber = NULL;
						__ns_ldap_freeEntry(ep);
						ep = NULL;
						if (gecos_mapping)
							__s_api_free2dArray(
							    gecos_mapping);
						gecos_mapping = NULL;
						return (NS_LDAP_MEMORY);
					}
				}
			} else {
				for (k = 0; k < ap[j]->value_count; k++) {
					if ((ap[j]->attrvalue[k] =
					    strdup(vals[k])) == NULL) {
						ber_free(ber, 0);
						ber = NULL;
						__ns_ldap_freeEntry(ep);
						ep = NULL;
						if (gecos_mapping)
							__s_api_free2dArray(
							    gecos_mapping);
						gecos_mapping = NULL;
						return (NS_LDAP_MEMORY);
					}
				}
			}

			ap[j]->attrvalue[k] = NULL;
			ldap_value_free(vals);
			vals = NULL;
		}

		ldap_memfree(attr);
		attr = NULL;
	}

	ber_free(ber, 0);
	ber = NULL;

	if (gecos_mapping) {
		__s_api_free2dArray(gecos_mapping);
		gecos_mapping = NULL;
	}

	/* special processing for gecos 1 to up to 3 attribute mapping */
	if (schema_mapping_existed && gecos_mapping_existed) {

		int	f = -1;

		for (i = 0; i < 3; i++) {
			k = gecos_val_index[i];

			/*
			 * f is the index of the first returned
			 * attribute which "gecos" attribute mapped to
			 */
			if (k != -1 && f == -1)
				f = k;

			if (k != -1 && ap[k]->value_count > 0 &&
			    ap[k]->attrvalue[0] &&
			    strlen(ap[k]->attrvalue[0]) > 0) {

				if (k == f) {
					/*
					 * Create and fill in the last reserved
					 * ap with the data from the "gecos"
					 * mapping attributes
					 */
					ap[nAttrs] = (ns_ldap_attr_t *)
					    calloc(1,
					    sizeof (ns_ldap_attr_t));
					if (ap[nAttrs] == NULL) {
						__ns_ldap_freeEntry(ep);
						ep = NULL;
						return (NS_LDAP_MEMORY);
					}
					ap[nAttrs]->attrvalue = (char **)calloc(
					    2, sizeof (char *));
					if (ap[nAttrs]->attrvalue == NULL) {
						__ns_ldap_freeEntry(ep);
						ep = NULL;
						return (NS_LDAP_MEMORY);
					}
					/* add 1 more for a possible "," */
					ap[nAttrs]->attrvalue[0] =
					    (char *)calloc(
					    strlen(ap[f]->attrvalue[0]) +
					    2, 1);
					if (ap[nAttrs]->attrvalue[0] == NULL) {
						__ns_ldap_freeEntry(ep);
						ep = NULL;
						return (NS_LDAP_MEMORY);
					}
					(void) strcpy(ap[nAttrs]->attrvalue[0],
					    ap[f]->attrvalue[0]);

					ap[nAttrs]->attrname = strdup("gecos");
					if (ap[nAttrs]->attrname == NULL) {
						__ns_ldap_freeEntry(ep);
						ep = NULL;
						return (NS_LDAP_MEMORY);
					}

					ap[nAttrs]->value_count = 1;
					ep->attr_count = nAttrs + 1;

				} else {
					char	*tmp = NULL;

					/*
					 * realloc to add "," and
					 * ap[k]->attrvalue[0]
					 */
					tmp = (char *)realloc(
					    ap[nAttrs]->attrvalue[0],
					    strlen(ap[nAttrs]->
					    attrvalue[0]) +
					    strlen(ap[k]->
					    attrvalue[0]) + 2);
					if (tmp == NULL) {
						__ns_ldap_freeEntry(ep);
						ep = NULL;
						return (NS_LDAP_MEMORY);
					}
					ap[nAttrs]->attrvalue[0] = tmp;
					(void) strcat(ap[nAttrs]->attrvalue[0],
					    ",");
					(void) strcat(ap[nAttrs]->attrvalue[0],
					    ap[k]->attrvalue[0]);
				}
			}
		}
	}

	*ret = ep;
	return (NS_LDAP_SUCCESS);
}

static int
__s_api_getEntry(ns_ldap_cookie_t *cookie)
{
	ns_ldap_entry_t	*curEntry = NULL;
	int		ret;

#ifdef DEBUG
	(void) fprintf(stderr, "__s_api_getEntry START\n");
#endif

	if (cookie->resultMsg == NULL) {
		return (NS_LDAP_INVALID_PARAM);
	}
	ret = __s_api_cvtEntry(cookie->conn->ld, cookie->service,
	    cookie->resultMsg, cookie->i_flags,
	    &curEntry, &cookie->errorp);
	if (ret != NS_LDAP_SUCCESS) {
		return (ret);
	}

	if (cookie->result == NULL) {
		cookie->result = (ns_ldap_result_t *)
		    calloc(1, sizeof (ns_ldap_result_t));
		if (cookie->result == NULL) {
			__ns_ldap_freeEntry(curEntry);
			curEntry = NULL;
			return (NS_LDAP_MEMORY);
		}
		cookie->result->entry = curEntry;
		cookie->nextEntry = curEntry;
	} else {
		cookie->nextEntry->next = curEntry;
		cookie->nextEntry = curEntry;
	}
	cookie->result->entries_count++;

	return (NS_LDAP_SUCCESS);
}

static int
__s_api_get_cachemgr_data(const char *type, const char *from, char **to)
{
	union {
		ldap_data_t	s_d;
		char		s_b[DOORBUFFERSIZE];
	} space;
	ldap_data_t	*sptr;
	int		ndata;
	int		adata;
	int		rc;

#ifdef DEBUG
	(void) fprintf(stderr, "__s_api_get_cachemgr_data START\n");
#endif
	/*
	 * We are not going to perform DN to domain mapping
	 * in the Standalone mode
	 */
	if (__s_api_isStandalone()) {
		return (-1);
	}

	if (from == NULL || from[0] == '\0' || to == NULL)
		return (-1);

	*to = NULL;
	(void) memset(space.s_b, 0, DOORBUFFERSIZE);

	space.s_d.ldap_call.ldap_callnumber = GETCACHE;
	(void) snprintf(space.s_d.ldap_call.ldap_u.domainname,
	    DOORBUFFERSIZE - sizeof (space.s_d.ldap_call.ldap_callnumber),
	    "%s%s%s",
	    type,
	    DOORLINESEP,
	    from);
	ndata = sizeof (space);
	adata = sizeof (ldap_call_t) +
	    strlen(space.s_d.ldap_call.ldap_u.domainname) + 1;
	sptr = &space.s_d;

	rc = __ns_ldap_trydoorcall(&sptr, &ndata, &adata);
	if (rc != NS_CACHE_SUCCESS)
		return (-1);
	else
		*to = strdup(sptr->ldap_ret.ldap_u.buff);
	return (NS_LDAP_SUCCESS);
}

static int
__s_api_set_cachemgr_data(const char *type, const char *from, const char *to)
{
	union {
		ldap_data_t	s_d;
		char		s_b[DOORBUFFERSIZE];
	} space;
	ldap_data_t	*sptr;
	int		ndata;
	int		adata;
	int		rc;

#ifdef DEBUG
	(void) fprintf(stderr, "__s_api_set_cachemgr_data START\n");
#endif
	/*
	 * We are not going to perform DN to domain mapping
	 * in the Standalone mode
	 */
	if (__s_api_isStandalone()) {
		return (-1);
	}

	if ((from == NULL) || (from[0] == '\0') ||
	    (to == NULL) || (to[0] == '\0'))
		return (-1);

	(void) memset(space.s_b, 0, DOORBUFFERSIZE);

	space.s_d.ldap_call.ldap_callnumber = SETCACHE;
	(void) snprintf(space.s_d.ldap_call.ldap_u.domainname,
	    DOORBUFFERSIZE - sizeof (space.s_d.ldap_call.ldap_callnumber),
	    "%s%s%s%s%s",
	    type,
	    DOORLINESEP,
	    from,
	    DOORLINESEP,
	    to);

	ndata = sizeof (space);
	adata = sizeof (ldap_call_t) +
	    strlen(space.s_d.ldap_call.ldap_u.domainname) + 1;
	sptr = &space.s_d;

	rc = __ns_ldap_trydoorcall(&sptr, &ndata, &adata);
	if (rc != NS_CACHE_SUCCESS)
		return (-1);

	return (NS_LDAP_SUCCESS);
}


static char *
__s_api_remove_rdn_space(char *rdn)
{
	char	*tf, *tl, *vf, *vl, *eqsign;

	/* if no space(s) to remove, return */
	if (strchr(rdn, SPACETOK) == NULL)
		return (rdn);

	/* if no '=' separator, return */
	eqsign = strchr(rdn, '=');
	if (eqsign == NULL)
		return (rdn);

	tf = rdn;
	tl = eqsign - 1;
	vf = eqsign + 1;
	vl = rdn + strlen(rdn) - 1;

	/* now two strings, type and value */
	*eqsign = '\0';

	/* remove type's leading spaces */
	while (tf < tl && *tf == SPACETOK)
		tf++;
	/* remove type's trailing spaces */
	while (tf < tl && *tl == SPACETOK)
		tl--;
	/* add '=' separator back */
	*(++tl) = '=';
	/* remove value's leading spaces */
	while (vf < vl && *vf == SPACETOK)
		vf++;
	/* remove value's trailing spaces */
	while (vf < vl && *vl == SPACETOK)
		*vl-- = '\0';

	/* move value up if necessary */
	if (vf != tl + 1)
		(void) strcpy(tl + 1, vf);

	return (tf);
}

static
ns_ldap_cookie_t *
init_search_state_machine()
{
	ns_ldap_cookie_t	*cookie;
	ns_config_t		*cfg;

	cookie = (ns_ldap_cookie_t *)calloc(1, sizeof (ns_ldap_cookie_t));
	if (cookie == NULL)
		return (NULL);
	cookie->state = INIT;
	/* assign other state variables */
	cfg = __s_api_loadrefresh_config();
	cookie->connectionId = -1;
	if (cfg == NULL ||
	    cfg->paramList[NS_LDAP_SEARCH_TIME_P].ns_ptype == NS_UNKNOWN) {
		cookie->search_timeout.tv_sec = NS_DEFAULT_SEARCH_TIMEOUT;
	} else {
		cookie->search_timeout.tv_sec =
		    cfg->paramList[NS_LDAP_SEARCH_TIME_P].ns_i;
	}
	if (cfg != NULL)
		__s_api_release_config(cfg);
	cookie->search_timeout.tv_usec = 0;

	return (cookie);
}

static void
delete_search_cookie(ns_ldap_cookie_t *cookie)
{
	if (cookie == NULL)
		return;
	if (cookie->connectionId > -1)
		DropConnection(cookie->connectionId, cookie->i_flags);
	if (cookie->filter)
		free(cookie->filter);
	if (cookie->i_filter)
		free(cookie->i_filter);
	if (cookie->service)
		free(cookie->service);
	if (cookie->sdlist)
		(void) __ns_ldap_freeSearchDescriptors(&(cookie->sdlist));
	if (cookie->result)
		(void) __ns_ldap_freeResult(&cookie->result);
	if (cookie->attribute)
		__s_api_free2dArray(cookie->attribute);
	if (cookie->errorp)
		(void) __ns_ldap_freeError(&cookie->errorp);
	if (cookie->reflist)
		__s_api_deleteRefInfo(cookie->reflist);
	if (cookie->basedn)
		free(cookie->basedn);
	if (cookie->ctrlCookie)
		ber_bvfree(cookie->ctrlCookie);
	_freeControlList(&cookie->p_serverctrls);
	if (cookie->resultctrl)
		ldap_controls_free(cookie->resultctrl);
	free(cookie);
}

static int
get_mapped_filter(ns_ldap_cookie_t *cookie, char **new_filter)
{

	typedef	struct	filter_mapping_info {
		char	oc_or_attr;
		char	*name_start;
		char	*name_end;
		char	*veq_pos;
		char	*from_name;
		char	*to_name;
		char	**mapping;
	} filter_mapping_info_t;

	char			*c, *last_copied;
	char			*filter_c, *filter_c_next;
	char			*key, *tail, *head;
	char			errstr[MAXERROR];
	int			num_eq = 0, num_veq = 0;
	boolean_t		in_quote = B_FALSE;
	boolean_t		is_value = B_FALSE;
	int			i, j, oc_len, len;
	boolean_t		at_least_one = B_FALSE;
	filter_mapping_info_t	**info, *info1;
	char			**mapping;
	char			*service, *filter, *err;
	boolean_t		auto_service = B_FALSE;

	if (cookie == NULL || new_filter == NULL)
		return (NS_LDAP_INVALID_PARAM);

	*new_filter = NULL;
	service = cookie->service;
	filter = cookie->filter;

	/*
	 * count the number of '=' char
	 */
	for (c = filter; *c; c++) {
		if (*c == TOKENSEPARATOR)
			num_eq++;
	}

	if (service != NULL && strncasecmp(service, "auto_", 5) == 0)
		auto_service = TRUE;

	/*
	 * See if schema mapping existed for the given service.
	 * If not, just return success.
	 */
	mapping = __ns_ldap_getOrigAttribute(service,
	    NS_HASH_SCHEMA_MAPPING_EXISTED);

	if (mapping == NULL && auto_service)
		/*
		 * if service == auto_* and no
		 * schema mapping found
		 * then try automount
		 */
		mapping = __ns_ldap_getOrigAttribute(
		    "automount", NS_HASH_SCHEMA_MAPPING_EXISTED);

	if (mapping)
		__s_api_free2dArray(mapping);
	else
		return (NS_LDAP_SUCCESS);

	/*
	 * no '=' sign, just say OK and return nothing
	 */
	if (num_eq == 0)
		return (NS_LDAP_SUCCESS);

	/*
	 * Make a copy of the filter string
	 * for saving the name of the objectclasses or
	 * attributes that need to be passed to the
	 * objectclass or attribute mapping functions.
	 * pointer "info->from_name" points to the locations
	 * within this string.
	 *
	 * The input filter string, filter, will be used
	 * to indicate where these names start and end.
	 * pointers "info->name_start" and "info->name_end"
	 * point to locations within the input filter string,
	 * and are used at the end of this function to
	 * merge the original filter data with the
	 * mapped objectclass or attribute names.
	 */
	filter_c = strdup(filter);
	if (filter_c == NULL)
		return (NS_LDAP_MEMORY);
	filter_c_next = filter_c;

	/*
	 * get memory for info arrays
	 */
	info = (filter_mapping_info_t **)calloc(num_eq + 1,
	    sizeof (filter_mapping_info_t *));

	if (info == NULL) {
		free(filter_c);
		return (NS_LDAP_MEMORY);
	}

	/*
	 * find valid '=' for further processing,
	 * ignore the "escaped =" (.i.e. "\="), or
	 * "=" in quoted string
	 */
	for (c = filter_c; *c; c++) {

		switch (*c) {
		case TOKENSEPARATOR:
			if (!in_quote && !is_value) {
				info1 = (filter_mapping_info_t *)calloc(1,
				    sizeof (filter_mapping_info_t));
				if (info1 == NULL) {
					free(filter_c);
					for (i = 0; i < num_veq; i++)
						free(info[i]);
					free(info);
					return (NS_LDAP_MEMORY);
				}
				info[num_veq] = info1;

				/*
				 * remember the location of this "="
				 */
				info[num_veq++]->veq_pos = c;

				/*
				 * skip until the end of the attribute value
				 */
				is_value = B_TRUE;
			}
			break;
		case CPARATOK:
			/*
			 * mark the end of the attribute value
			 */
			if (!in_quote)
				is_value = B_FALSE;
			break;
		case QUOTETOK:
			/*
			 * switch on/off the in_quote mode
			 */
			in_quote = (in_quote == B_FALSE);
			break;
		case '\\':
			/*
			 * ignore escape characters
			 * don't skip if next char is '\0'
			 */
			if (!in_quote)
				if (*(++c) == '\0')
					c--;
			break;
		}

	}

	/*
	 * for each valid "=" found, get the name to
	 * be mapped
	 */
	oc_len = strlen("objectclass");
	for (i = 0; i < num_veq; i++) {

		/*
		 * look at the left side of "=" to see
		 * if assertion is "objectclass=<ocname>"
		 * or "<attribute name>=<attribute value>"
		 *
		 * first skip spaces before "=".
		 * Note that filter_c_next may not point to the
		 * start of the filter string. For i > 0,
		 * it points to the end of the last name processed + 2
		 */
		for (tail = info[i]->veq_pos; (tail > filter_c_next) &&
		    (*(tail - 1) == SPACETOK); tail--)
			;

		/*
		 * mark the end of the left side string (the key)
		 */
		*tail = '\0';
		info[i]->name_end = tail - filter_c - 1 + filter;

		/*
		 * find the start of the key
		 */
		key = filter_c_next;
		for (c = tail; filter_c_next <= c; c--) {
			/* OPARATOK is '(' */
			if (*c == OPARATOK ||
			    *c == SPACETOK) {
				key = c + 1;
				break;
			}
		}
		info[i]->name_start = key - filter_c + filter;

		if ((key + oc_len) <= tail) {
			if (strncasecmp(key, "objectclass",
			    oc_len) == 0) {
				/*
				 * assertion is "objectclass=ocname",
				 * ocname is the one needs to be mapped
				 *
				 * skip spaces after "=" to find start
				 * of the ocname
				 */
				head = info[i]->veq_pos;
				for (head = info[i]->veq_pos + 1;
				    *head && *head == SPACETOK; head++)
					;

				/* ignore empty ocname */
				if (!(*head))
					continue;

				info[i]->name_start = head - filter_c +
				    filter;

				/*
				 * now find the end of the ocname
				 */
				for (c = head; ; c++) {
					/* CPARATOK is ')' */
					if (*c == CPARATOK ||
					    *c == '\0' ||
					    *c == SPACETOK) {
						*c = '\0';
						info[i]->name_end =
						    c - filter_c - 1 +
						    filter;
						filter_c_next = c + 1;
						info[i]->oc_or_attr = 'o';
						info[i]->from_name = head;
						break;
					}
				}
			}
		}

		/*
		 * assertion is not "objectclass=ocname",
		 * assume assertion is "<key> = <value>",
		 * <key> is the one needs to be mapped
		 */
		if (info[i]->from_name == NULL && strlen(key) > 0) {
			info[i]->oc_or_attr = 'a';
			info[i]->from_name = key;
		}
	}

	/* perform schema mapping */
	for (i = 0; i < num_veq; i++) {
		if (info[i]->from_name == NULL)
			continue;

		if (info[i]->oc_or_attr == 'a')
			info[i]->mapping =
			    __ns_ldap_getMappedAttributes(service,
			    info[i]->from_name);
		else
			info[i]->mapping =
			    __ns_ldap_getMappedObjectClass(service,
			    info[i]->from_name);

		if (info[i]->mapping == NULL && auto_service)  {
			/*
			 * If no mapped attribute/objectclass is found
			 * and service == auto*
			 * try to find automount's
			 * mapped attribute/objectclass
			 */
			if (info[i]->oc_or_attr == 'a')
				info[i]->mapping =
				    __ns_ldap_getMappedAttributes("automount",
				    info[i]->from_name);
			else
				info[i]->mapping =
				    __ns_ldap_getMappedObjectClass("automount",
				    info[i]->from_name);
		}

		if (info[i]->mapping == NULL ||
		    info[i]->mapping[0] == NULL) {
			info[i]->to_name = NULL;
		} else if (info[i]->mapping[1] == NULL) {
			info[i]->to_name = info[i]->mapping[0];
			at_least_one = TRUE;
		} else {
			__s_api_free2dArray(info[i]->mapping);
			/*
			 * multiple mapping
			 * not allowed
			 */
			(void) sprintf(errstr,
			    gettext(
			    "Multiple attribute or objectclass "
			    "mapping for '%s' in filter "
			    "'%s' not allowed."),
			    info[i]->from_name, filter);
			err = strdup(errstr);
			if (err) {
				MKERROR(LOG_WARNING, cookie->errorp,
				    NS_CONFIG_SYNTAX,
				    err, NS_LDAP_MEMORY);
			}

			free(filter_c);
			for (j = 0; j < num_veq; j++) {
				if (info[j]->mapping)
					__s_api_free2dArray(
					    info[j]->mapping);
				free(info[j]);
			}
			free(info);
			return (NS_LDAP_CONFIG);
		}
	}


	if (at_least_one) {

		len = strlen(filter);
		last_copied = filter - 1;

		for (i = 0; i < num_veq; i++) {
			if (info[i]->to_name)
				len += strlen(info[i]->to_name);
		}

		*new_filter = (char *)calloc(1, len);
		if (*new_filter == NULL) {
			free(filter_c);
			for (j = 0; j < num_veq; j++) {
				if (info[j]->mapping)
					__s_api_free2dArray(
					    info[j]->mapping);
				free(info[j]);
			}
			free(info);
			return (NS_LDAP_MEMORY);
		}

		for (i = 0; i < num_veq; i++) {
			if (info[i]->to_name != NULL &&
			    info[i]->to_name != NULL) {

				/*
				 * copy the original filter data
				 * between the last name and current
				 * name
				 */
				if ((last_copied + 1) != info[i]->name_start)
					(void) strncat(*new_filter,
					    last_copied + 1,
					    info[i]->name_start -
					    last_copied - 1);

				/* the data is copied */
				last_copied = info[i]->name_end;

				/*
				 * replace the name with
				 * the mapped name
				 */
				(void) strcat(*new_filter, info[i]->to_name);
			}

			/* copy the filter data after the last name */
			if (i == (num_veq -1) &&
			    info[i]->name_end <
			    (filter + strlen(filter)))
				(void) strncat(*new_filter, last_copied + 1,
				    filter + strlen(filter) -
				    last_copied - 1);
		}

	}

	/* free memory */
	free(filter_c);
	for (j = 0; j < num_veq; j++) {
		if (info[j]->mapping)
			__s_api_free2dArray(info[j]->mapping);
		free(info[j]);
	}
	free(info);

	return (NS_LDAP_SUCCESS);
}

static int
setup_next_search(ns_ldap_cookie_t *cookie)
{
	ns_ldap_search_desc_t	*dptr;
	int			scope;
	char			*filter, *str;
	int			baselen;
	int			rc;
	void			**param;

	dptr = *cookie->sdpos;
	scope = cookie->i_flags & (NS_LDAP_SCOPE_BASE |
	    NS_LDAP_SCOPE_ONELEVEL |
	    NS_LDAP_SCOPE_SUBTREE);
	if (scope)
		cookie->scope = scope;
	else
		cookie->scope = dptr->scope;
	switch (cookie->scope) {
	case NS_LDAP_SCOPE_BASE:
		cookie->scope = LDAP_SCOPE_BASE;
		break;
	case NS_LDAP_SCOPE_ONELEVEL:
		cookie->scope = LDAP_SCOPE_ONELEVEL;
		break;
	case NS_LDAP_SCOPE_SUBTREE:
		cookie->scope = LDAP_SCOPE_SUBTREE;
		break;
	}

	filter = NULL;
	if (cookie->use_filtercb && cookie->init_filter_cb &&
	    dptr->filter && strlen(dptr->filter) > 0) {
		(*cookie->init_filter_cb)(dptr, &filter,
		    cookie->userdata);
	}
	if (filter == NULL) {
		if (cookie->i_filter == NULL) {
			cookie->err_rc = NS_LDAP_INVALID_PARAM;
			return (-1);
		} else {
			if (cookie->filter)
				free(cookie->filter);
			cookie->filter = strdup(cookie->i_filter);
			if (cookie->filter == NULL) {
				cookie->err_rc = NS_LDAP_MEMORY;
				return (-1);
			}
		}
	} else {
		if (cookie->filter)
			free(cookie->filter);
		cookie->filter = strdup(filter);
		free(filter);
		if (cookie->filter == NULL) {
			cookie->err_rc = NS_LDAP_MEMORY;
			return (-1);
		}
	}

	/*
	 * perform attribute/objectclass mapping on filter
	 */
	filter = NULL;

	if (cookie->service) {
		rc = get_mapped_filter(cookie, &filter);
		if (rc != NS_LDAP_SUCCESS) {
			cookie->err_rc = rc;
			return (-1);
		} else {
			/*
			 * get_mapped_filter returns
			 * NULL filter pointer, if
			 * no mapping was done
			 */
			if (filter) {
				free(cookie->filter);
				cookie->filter = filter;
			}
		}
	}

	/*
	 * validate filter to make sure it's legal
	 * [remove redundant ()'s]
	 */
	rc = validate_filter(cookie);
	if (rc != NS_LDAP_SUCCESS) {
		cookie->err_rc = rc;
		return (-1);
	}

	baselen = strlen(dptr->basedn);
	if (baselen > 0 && dptr->basedn[baselen-1] == COMMATOK) {
		rc = __ns_ldap_getParam(NS_LDAP_SEARCH_BASEDN_P,
		    (void ***)&param, &cookie->errorp);
		if (rc != NS_LDAP_SUCCESS) {
			cookie->err_rc = rc;
			return (-1);
		}
		str = ((char **)param)[0];
		baselen += strlen(str)+1;
		if (cookie->basedn)
			free(cookie->basedn);
		cookie->basedn = (char *)malloc(baselen);
		if (cookie->basedn == NULL) {
			cookie->err_rc = NS_LDAP_MEMORY;
			return (-1);
		}
		(void) strcpy(cookie->basedn, dptr->basedn);
		(void) strcat(cookie->basedn, str);
		(void) __ns_ldap_freeParam(&param);
	} else {
		if (cookie->basedn)
			free(cookie->basedn);
		cookie->basedn = strdup(dptr->basedn);
	}
	return (0);
}

static int
setup_referral_search(ns_ldap_cookie_t *cookie)
{
	ns_referral_info_t	*ref;

	ref = cookie->refpos;
	cookie->scope = ref->refScope;
	if (cookie->filter) {
		free(cookie->filter);
	}
	cookie->filter = strdup(ref->refFilter);
	if (cookie->basedn) {
		free(cookie->basedn);
	}
	cookie->basedn = strdup(ref->refDN);
	if (cookie->filter == NULL || cookie->basedn == NULL) {
		cookie->err_rc = NS_LDAP_MEMORY;
		return (-1);
	}
	return (0);
}

static int
get_current_session(ns_ldap_cookie_t *cookie)
{
	ConnectionID	connectionId = -1;
	Connection	*conp = NULL;
	int		rc;
	int		fail_if_new_pwd_reqd = 1;

	rc = __s_api_getConnection(NULL, cookie->i_flags,
	    cookie->i_auth, &connectionId, &conp,
	    &cookie->errorp, fail_if_new_pwd_reqd,
	    cookie->nopasswd_acct_mgmt, cookie->conn_user);

	/*
	 * If password control attached in *cookie->errorp,
	 * e.g. rc == NS_LDAP_SUCCESS_WITH_INFO,
	 * free the error structure (we do not need
	 * the sec_to_expired info).
	 * Reset rc to NS_LDAP_SUCCESS.
	 */
	if (rc == NS_LDAP_SUCCESS_WITH_INFO) {
		(void) __ns_ldap_freeError(
		    &cookie->errorp);
		cookie->errorp = NULL;
		rc = NS_LDAP_SUCCESS;
	}

	if (rc != NS_LDAP_SUCCESS) {
		cookie->err_rc = rc;
		return (-1);
	}
	cookie->conn = conp;
	cookie->connectionId = connectionId;

	return (0);
}

static int
get_next_session(ns_ldap_cookie_t *cookie)
{
	ConnectionID	connectionId = -1;
	Connection	*conp = NULL;
	int		rc;
	int		fail_if_new_pwd_reqd = 1;

	if (cookie->connectionId > -1) {
		DropConnection(cookie->connectionId, cookie->i_flags);
		cookie->connectionId = -1;
	}

	/* If using a MT connection, return it. */
	if (cookie->conn_user != NULL &&
	    cookie->conn_user->conn_mt != NULL)
		__s_api_conn_mt_return(cookie->conn_user);

	rc = __s_api_getConnection(NULL, cookie->i_flags,
	    cookie->i_auth, &connectionId, &conp,
	    &cookie->errorp, fail_if_new_pwd_reqd,
	    cookie->nopasswd_acct_mgmt, cookie->conn_user);

	/*
	 * If password control attached in *cookie->errorp,
	 * e.g. rc == NS_LDAP_SUCCESS_WITH_INFO,
	 * free the error structure (we do not need
	 * the sec_to_expired info).
	 * Reset rc to NS_LDAP_SUCCESS.
	 */
	if (rc == NS_LDAP_SUCCESS_WITH_INFO) {
		(void) __ns_ldap_freeError(
		    &cookie->errorp);
		cookie->errorp = NULL;
		rc = NS_LDAP_SUCCESS;
	}

	if (rc != NS_LDAP_SUCCESS) {
		cookie->err_rc = rc;
		return (-1);
	}
	cookie->conn = conp;
	cookie->connectionId = connectionId;
	return (0);
}

static int
get_referral_session(ns_ldap_cookie_t *cookie)
{
	ConnectionID	connectionId = -1;
	Connection	*conp = NULL;
	int		rc;
	int		fail_if_new_pwd_reqd = 1;

	if (cookie->connectionId > -1) {
		DropConnection(cookie->connectionId, cookie->i_flags);
		cookie->connectionId = -1;
	}

	/* set it up to use a connection opened for referral */
	if (cookie->conn_user != NULL) {
		/* If using a MT connection, return it. */
		if (cookie->conn_user->conn_mt != NULL)
			__s_api_conn_mt_return(cookie->conn_user);
		cookie->conn_user->referral = B_TRUE;
	}

	rc = __s_api_getConnection(cookie->refpos->refHost, 0,
	    cookie->i_auth, &connectionId, &conp,
	    &cookie->errorp, fail_if_new_pwd_reqd,
	    cookie->nopasswd_acct_mgmt, cookie->conn_user);

	/*
	 * If password control attached in *cookie->errorp,
	 * e.g. rc == NS_LDAP_SUCCESS_WITH_INFO,
	 * free the error structure (we do not need
	 * the sec_to_expired info).
	 * Reset rc to NS_LDAP_SUCCESS.
	 */
	if (rc == NS_LDAP_SUCCESS_WITH_INFO) {
		(void) __ns_ldap_freeError(
		    &cookie->errorp);
		cookie->errorp = NULL;
		rc = NS_LDAP_SUCCESS;
	}

	if (rc != NS_LDAP_SUCCESS) {
		cookie->err_rc = rc;
		return (-1);
	}
	cookie->conn = conp;
	cookie->connectionId = connectionId;
	return (0);
}

static int
paging_supported(ns_ldap_cookie_t *cookie)
{
	int		rc;

	cookie->listType = 0;
	rc = __s_api_isCtrlSupported(cookie->conn,
	    LDAP_CONTROL_VLVREQUEST);
	if (rc == NS_LDAP_SUCCESS) {
		cookie->listType = VLVCTRLFLAG;
		return (1);
	}
#if	0
	/* FIXME: */
	rc = __s_api_isCtrlSupported(cookie->conn,
	    LDAP_CONTROL_SIMPLE_PAGE);
	if (rc == NS_LDAP_SUCCESS) {
		cookie->listType = SIMPLEPAGECTRLFLAG;
		return (1);
	}
#endif
	return (0);
}

typedef struct servicesorttype {
	char *service;
	ns_srvsidesort_t type;
} servicesorttype_t;

static servicesorttype_t *sort_type = NULL;
static int sort_type_size = 0;
static int sort_type_hwm = 0;
static mutex_t sort_type_mutex = DEFAULTMUTEX;


static ns_srvsidesort_t
get_srvsidesort_type(char *service)
{
	int i;
	ns_srvsidesort_t type = SSS_UNKNOWN;

	if (service == NULL)
		return (type);

	(void) mutex_lock(&sort_type_mutex);
	if (sort_type != NULL) {
		for (i = 0; i < sort_type_hwm; i++) {
			if (strcmp(sort_type[i].service, service) == 0) {
				type = sort_type[i].type;
				break;
			}
		}
	}
	(void) mutex_unlock(&sort_type_mutex);
	return (type);
}

static void
update_srvsidesort_type(char *service, ns_srvsidesort_t type)
{
	int i, size;
	servicesorttype_t *tmp;

	if (service == NULL)
		return;

	(void) mutex_lock(&sort_type_mutex);

	for (i = 0; i < sort_type_hwm; i++) {
		if (strcmp(sort_type[i].service, service) == 0) {
			sort_type[i].type = type;
			(void) mutex_unlock(&sort_type_mutex);
			return;
		}
	}
	if (sort_type == NULL) {
		size = 10;
		tmp = malloc(size * sizeof (servicesorttype_t));
		if (tmp == NULL) {
			(void) mutex_unlock(&sort_type_mutex);
			return;
		}
		sort_type = tmp;
		sort_type_size = size;
	} else if (sort_type_hwm >= sort_type_size) {
		size = sort_type_size + 10;
		tmp = realloc(sort_type, size * sizeof (servicesorttype_t));
		if (tmp == NULL) {
			(void) mutex_unlock(&sort_type_mutex);
			return;
		}
		sort_type = tmp;
		sort_type_size = size;
	}
	sort_type[sort_type_hwm].service = strdup(service);
	if (sort_type[sort_type_hwm].service == NULL) {
		(void) mutex_unlock(&sort_type_mutex);
		return;
	}
	sort_type[sort_type_hwm].type = type;
	sort_type_hwm++;

	(void) mutex_unlock(&sort_type_mutex);
}

static int
setup_vlv_params(ns_ldap_cookie_t *cookie)
{
	LDAPControl	**ctrls;
	LDAPSortKey	**sortkeylist;
	LDAPControl	*sortctrl = NULL;
	LDAPControl	*vlvctrl = NULL;
	LDAPVirtualList	vlist;
	char		*sortattr;
	int		rc;
	int		free_sort = FALSE;

	_freeControlList(&cookie->p_serverctrls);

	if (cookie->sortTypeTry == SSS_UNKNOWN)
		cookie->sortTypeTry = get_srvsidesort_type(cookie->service);
	if (cookie->sortTypeTry == SSS_UNKNOWN)
		cookie->sortTypeTry = SSS_SINGLE_ATTR;

	if (cookie->sortTypeTry == SSS_SINGLE_ATTR) {
		if ((cookie->i_flags & NS_LDAP_NOMAP) == 0 &&
		    cookie->i_sortattr) {
			sortattr =  __ns_ldap_mapAttribute(cookie->service,
			    cookie->i_sortattr);
			free_sort = TRUE;
		} else if (cookie->i_sortattr) {
			sortattr = (char *)cookie->i_sortattr;
		} else {
			sortattr = "cn";
		}
	} else {
		sortattr = "cn uid";
	}

	rc = ldap_create_sort_keylist(&sortkeylist, sortattr);
	if (free_sort)
		free(sortattr);
	if (rc != LDAP_SUCCESS) {
		(void) ldap_get_option(cookie->conn->ld,
		    LDAP_OPT_ERROR_NUMBER, &rc);
		return (rc);
	}
	rc = ldap_create_sort_control(cookie->conn->ld,
	    sortkeylist, 1, &sortctrl);
	ldap_free_sort_keylist(sortkeylist);
	if (rc != LDAP_SUCCESS) {
		(void) ldap_get_option(cookie->conn->ld,
		    LDAP_OPT_ERROR_NUMBER, &rc);
		return (rc);
	}

	vlist.ldvlist_index = cookie->index;
	vlist.ldvlist_size = 0;

	vlist.ldvlist_before_count = 0;
	vlist.ldvlist_after_count = LISTPAGESIZE-1;
	vlist.ldvlist_attrvalue = NULL;
	vlist.ldvlist_extradata = NULL;

	abort();
	(void)vlist;
/*	rc = ldap_create_virtuallist_control(cookie->conn->ld,
	    &vlist, &vlvctrl);*/
	if (rc != LDAP_SUCCESS) {
		ldap_control_free(sortctrl);
		(void) ldap_get_option(cookie->conn->ld, LDAP_OPT_ERROR_NUMBER,
		    &rc);
		return (rc);
	}

	ctrls = (LDAPControl **)calloc(3, sizeof (LDAPControl *));
	if (ctrls == NULL) {
		ldap_control_free(sortctrl);
		ldap_control_free(vlvctrl);
		return (LDAP_NO_MEMORY);
	}

	ctrls[0] = sortctrl;
	ctrls[1] = vlvctrl;

	cookie->p_serverctrls = ctrls;
	return (LDAP_SUCCESS);
}

static int
setup_simplepg_params(ns_ldap_cookie_t *cookie)
{
	LDAPControl	**ctrls;
	LDAPControl	*pgctrl = NULL;
	int		rc;

	_freeControlList(&cookie->p_serverctrls);

	rc = ldap_create_page_control(cookie->conn->ld, LISTPAGESIZE,
	    cookie->ctrlCookie, (char)0, &pgctrl);
	if (rc != LDAP_SUCCESS) {
		(void) ldap_get_option(cookie->conn->ld, LDAP_OPT_ERROR_NUMBER,
		    &rc);
		return (rc);
	}

	ctrls = (LDAPControl **)calloc(2, sizeof (LDAPControl *));
	if (ctrls == NULL) {
		ldap_control_free(pgctrl);
		return (LDAP_NO_MEMORY);
	}
	ctrls[0] = pgctrl;
	cookie->p_serverctrls = ctrls;
	return (LDAP_SUCCESS);
}

static void
proc_result_referrals(ns_ldap_cookie_t *cookie)
{
	int		errCode, i, rc;
	char		**referrals = NULL;

	/*
	 * Only follow one level of referrals, i.e.
	 * if already in referral mode, do nothing
	 */
	if (cookie->refpos == NULL) {
		cookie->new_state = END_RESULT;
		rc = ldap_parse_result(cookie->conn->ld,
		    cookie->resultMsg,
		    &errCode, NULL,
		    NULL, &referrals,
		    NULL, 0);
		if (rc != NS_LDAP_SUCCESS) {
			(void) ldap_get_option(cookie->conn->ld,
			    LDAP_OPT_ERROR_NUMBER,
			    &cookie->err_rc);
			cookie->new_state = LDAP_ERROR;
			return;
		}
		if (errCode == LDAP_REFERRAL) {
			for (i = 0; referrals[i] != NULL;
			    i++) {
				/* add to referral list */
				rc = __s_api_addRefInfo(
				    &cookie->reflist,
				    referrals[i],
				    cookie->basedn,
				    &cookie->scope,
				    cookie->filter,
				    cookie->conn->ld);
				if (rc != NS_LDAP_SUCCESS) {
					cookie->new_state =
					    ERROR;
					break;
				}
			}
			ldap_value_free(referrals);
		}
	}
}

#ifdef _SOLARIS_SDK

static char **
ldap_get_reference_urls(LDAP *ld, LDAPMessage *res)
{
	BerElement	*tmp;
	char		**urls = NULL, *ptr;
	int		code = LDAP_SUCCESS;

	/*LDAPDebug( LDAP_DEBUG_TRACE, "ldap_get_reference_urls\n", 0, 0, 0 );*/

	if (res == NULL){
		code = LDAP_PARAM_ERROR;
		ldap_set_option(ld, LDAP_OPT_RESULT_CODE, &code);
		return (NULL);
	}
	tmp = NULL;
	ptr = ldap_first_attribute(ld, res, &tmp);
	if (ptr == NULL) {
		return (NULL);
	}
	if ( ber_scanf(tmp, "{v}", &urls) == LBER_ERROR){
		ber_free(tmp, 1);
		ldap_memfree(ptr);
		code = LDAP_DECODING_ERROR;
		ldap_set_option(ld, LDAP_OPT_RESULT_CODE, &code);
		return (NULL);
	}
	ber_free(tmp, 1);
	ldap_memfree(ptr);
	return (urls);
}

#endif /* _SOLARIS_SDK */

static void
proc_search_references(ns_ldap_cookie_t *cookie)
{
	char		**refurls = NULL;
	int		i, rc;

	/*
	 * Only follow one level of referrals, i.e.
	 * if already in referral mode, do nothing
	 */
	if (cookie->refpos == NULL) {
		refurls = ldap_get_reference_urls(
		    cookie->conn->ld,
		    cookie->resultMsg);
		if (refurls == NULL) {
			(void) ldap_get_option(cookie->conn->ld,
			    LDAP_OPT_ERROR_NUMBER,
			    &cookie->err_rc);
			cookie->new_state = LDAP_ERROR;
			return;
		}
		for (i = 0; refurls[i] != NULL; i++) {
			/* add to referral list */
			rc = __s_api_addRefInfo(
			    &cookie->reflist,
			    refurls[i],
			    cookie->basedn,
			    &cookie->scope,
			    cookie->filter,
			    cookie->conn->ld);
			if (rc != NS_LDAP_SUCCESS) {
				cookie->new_state =
				    ERROR;
				break;
			}
		}
		/* free allocated storage */
		for (i = 0; refurls[i] != NULL; i++)
			free(refurls[i]);
	}
}

static ns_state_t
multi_result(ns_ldap_cookie_t *cookie)
{
	char		errstr[MAXERROR];
	char		*err;
	ns_ldap_error_t **errorp = NULL;
	LDAPControl	**retCtrls = NULL;
	int		i, rc;
	int		errCode;
	boolean_t	finished = B_FALSE;
	unsigned long	target_posp = 0;
	unsigned long	list_size = 0;
	ber_int_t	count = 0;
	// unsigned int	count = 0;
	char		**referrals = NULL;

	if (cookie->listType == VLVCTRLFLAG) {
		rc = ldap_parse_result(cookie->conn->ld, cookie->resultMsg,
		    &errCode, NULL, NULL, &referrals, &retCtrls, 0);
		if (rc != LDAP_SUCCESS) {
			(void) ldap_get_option(cookie->conn->ld,
			    LDAP_OPT_ERROR_NUMBER,
			    &cookie->err_rc);
			(void) sprintf(errstr,
			    gettext("LDAP ERROR (%d): %s.\n"),
			    cookie->err_rc,
			    gettext(ldap_err2string(cookie->err_rc)));
			err = strdup(errstr);
			MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, err,
			    LDAP_ERROR);
			cookie->err_rc = NS_LDAP_INTERNAL;
			cookie->errorp = *errorp;
			return (LDAP_ERROR);
		}
		if (errCode == LDAP_REFERRAL) {
			for (i = 0; referrals[i] != NULL;
			    i++) {
				/* add to referral list */
				rc = __s_api_addRefInfo(
				    &cookie->reflist,
				    referrals[i],
				    cookie->basedn,
				    &cookie->scope,
				    cookie->filter,
				    cookie->conn->ld);
				if (rc != NS_LDAP_SUCCESS) {
					ldap_value_free(
					    referrals);
					if (retCtrls)
						ldap_controls_free(
						    retCtrls);
					return (ERROR);
				}
			}
			ldap_value_free(referrals);
			if (retCtrls)
				ldap_controls_free(retCtrls);
			return (END_RESULT);
		}
		if (retCtrls) {
			abort();
			/*rc = ldap_parse_virtuallist_control(
			    cookie->conn->ld, retCtrls,
			    &target_posp, &list_size, &errCode);*/
			if (rc == LDAP_SUCCESS) {
				/*
				 * AD does not return valid target_posp
				 * and list_size
				 */
				if (target_posp != 0 && list_size != 0) {
					cookie->index =
					    target_posp + LISTPAGESIZE;
					if (cookie->index > list_size)
						finished = B_TRUE;
				} else {
					if (cookie->entryCount < LISTPAGESIZE)
						finished = B_TRUE;
					else
						cookie->index +=
						    cookie->entryCount;
				}
			}
			ldap_controls_free(retCtrls);
			retCtrls = NULL;
		} else {
			finished = B_TRUE;
		}
	} else if (cookie->listType == SIMPLEPAGECTRLFLAG) {
		rc = ldap_parse_result(cookie->conn->ld, cookie->resultMsg,
		    &errCode, NULL, NULL, &referrals, &retCtrls, 0);
		if (rc != LDAP_SUCCESS) {
			(void) ldap_get_option(cookie->conn->ld,
			    LDAP_OPT_ERROR_NUMBER,
			    &cookie->err_rc);
			(void) sprintf(errstr,
			    gettext("LDAP ERROR (%d): %s.\n"),
			    cookie->err_rc,
			    gettext(ldap_err2string(cookie->err_rc)));
			err = strdup(errstr);
			MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, err,
			    LDAP_ERROR);
			cookie->err_rc = NS_LDAP_INTERNAL;
			cookie->errorp = *errorp;
			return (LDAP_ERROR);
		}
		if (errCode == LDAP_REFERRAL) {
			for (i = 0; referrals[i] != NULL;
			    i++) {
				/* add to referral list */
				rc = __s_api_addRefInfo(
				    &cookie->reflist,
				    referrals[i],
				    cookie->basedn,
				    &cookie->scope,
				    cookie->filter,
				    cookie->conn->ld);
				if (rc != NS_LDAP_SUCCESS) {
					ldap_value_free(
					    referrals);
					if (retCtrls)
						ldap_controls_free(
						    retCtrls);
					return (ERROR);
				}
			}
			ldap_value_free(referrals);
			if (retCtrls)
				ldap_controls_free(retCtrls);
			return (END_RESULT);
		}
		if (retCtrls) {
			if (cookie->ctrlCookie)
				ber_bvfree(cookie->ctrlCookie);
			cookie->ctrlCookie = NULL;
			rc = ldap_parse_page_control(
			    cookie->conn->ld, retCtrls,
			    &count, &cookie->ctrlCookie);
			if (rc == LDAP_SUCCESS) {
				if ((cookie->ctrlCookie == NULL) ||
				    (cookie->ctrlCookie->bv_val == NULL) ||
				    (cookie->ctrlCookie->bv_len == 0))
					finished = B_TRUE;
			}
			ldap_controls_free(retCtrls);
			retCtrls = NULL;
		} else {
			finished = B_TRUE;
		}
	}
	if (!finished && cookie->listType == VLVCTRLFLAG)
		return (NEXT_VLV);
	if (!finished && cookie->listType == SIMPLEPAGECTRLFLAG)
		return (NEXT_PAGE);
	if (finished)
		return (END_RESULT);
	return (ERROR);
}

/*
 * clear_results(ns_ldap_cookie_t):
 *
 * Attempt to obtain remnants of ldap responses and free them.  If remnants are
 * not obtained within a certain time period tell the server we wish to abandon
 * the request.
 *
 * Note that we do not initially tell the server to abandon the request as that
 * can be an expensive operation for the server, while it is cheap for us to
 * just flush the input.
 *
 * If something was to remain in libldap queue as a result of some error then
 * it would be freed later during drop connection call or when no other
 * requests share the connection.
 */
static void
clear_results(ns_ldap_cookie_t *cookie)
{
	int rc;
	if (cookie->conn != NULL && cookie->conn->ld != NULL &&
	    (cookie->connectionId != -1 ||
	    (cookie->conn_user != NULL &&
	    cookie->conn_user->conn_mt != NULL)) &&
	    cookie->msgId != 0) {
		/*
		 * We need to cleanup the rest of response (if there is such)
		 * and LDAP abandon is too heavy for LDAP servers, so we will
		 * wait for the rest of response till timeout and "process" it.
		 */
		rc = ldap_result(cookie->conn->ld, cookie->msgId, LDAP_MSG_ALL,
		    (struct timeval *)&cookie->search_timeout,
		    &cookie->resultMsg);
		if (rc != -1 && rc != 0 && cookie->resultMsg != NULL) {
			(void) ldap_msgfree(cookie->resultMsg);
			cookie->resultMsg = NULL;
		}

		/*
		 * If there was timeout then we will send  ABANDON request to
		 * LDAP server to decrease load.
		 */
		if (rc == 0)
			(void) ldap_abandon_ext(cookie->conn->ld, cookie->msgId,
			    NULL, NULL);
		/* Disassociate cookie with msgId */
		cookie->msgId = 0;
	}
}

/*
 * This state machine performs one or more LDAP searches to a given
 * directory server using service search descriptors and schema
 * mapping as appropriate.  The approximate pseudocode for
 * this routine is the following:
 *    Given the current configuration [set/reset connection etc.]
 *    and the current service search descriptor list
 *        or default search filter parameters
 *    foreach (service search filter) {
 *        initialize the filter [via filter_init if appropriate]
 *		  get a valid session/connection (preferably the current one)
 *					Recover if the connection is lost
 *        perform the search
 *        foreach (result entry) {
 *            process result [via callback if appropriate]
 *                save result for caller if accepted.
 *                exit and return all collected if allResults found;
 *        }
 *    }
 *    return collected results and exit
 */

static
ns_state_t
search_state_machine(ns_ldap_cookie_t *cookie, ns_state_t state, int cycle)
{
	char		errstr[MAXERROR];
	char		*err;
	int		rc, ret;
	int		rc_save;
	ns_ldap_entry_t	*nextEntry;
	ns_ldap_error_t *error = NULL;
	ns_ldap_error_t **errorp;
	struct timeval	tv;

	errorp = &error;
	cookie->state = state;
	errstr[0] = '\0';

	for (;;) {
		switch (cookie->state) {
		case CLEAR_RESULTS:
			clear_results(cookie);
			cookie->new_state = EXIT;
			break;
		case GET_ACCT_MGMT_INFO:
			/*
			 * Set the flag to get ldap account management controls.
			 */
			cookie->nopasswd_acct_mgmt = 1;
			cookie->new_state = INIT;
			break;
		case EXIT:
			/* state engine/connection cleaned up in delete */
			if (cookie->attribute) {
				__s_api_free2dArray(cookie->attribute);
				cookie->attribute = NULL;
			}
			if (cookie->reflist) {
				__s_api_deleteRefInfo(cookie->reflist);
				cookie->reflist = NULL;
			}
			return (EXIT);
		case INIT:
			cookie->sdpos = NULL;
			cookie->new_state = NEXT_SEARCH_DESCRIPTOR;
			if (cookie->attribute) {
				__s_api_free2dArray(cookie->attribute);
				cookie->attribute = NULL;
			}
			if ((cookie->i_flags & NS_LDAP_NOMAP) == 0 &&
			    cookie->i_attr) {
				cookie->attribute =
				    __ns_ldap_mapAttributeList(
				    cookie->service,
				    cookie->i_attr);
			}
			break;
		case REINIT:
			/* Check if we've reached MAX retries. */
			cookie->retries++;
			if (cookie->retries > NS_LIST_TRY_MAX - 1) {
				cookie->new_state = LDAP_ERROR;
				break;
			}

			/*
			 * Even if we still have retries left, check
			 * if retry is possible.
			 */
			if (cookie->conn_user != NULL) {
				int		retry;
				ns_conn_mgmt_t	*cmg;
				cmg = cookie->conn_user->conn_mgmt;
				retry = cookie->conn_user->retry;
				if (cmg != NULL && cmg->cfg_reloaded == 1)
					retry = 1;
				if (retry == 0) {
					cookie->new_state = LDAP_ERROR;
					break;
				}
			}
			/*
			 * Free results if any, reset to the first
			 * search descriptor and start a new session.
			 */
			if (cookie->resultMsg != NULL) {
				(void) ldap_msgfree(cookie->resultMsg);
				cookie->resultMsg = NULL;
			}
			(void) __ns_ldap_freeError(&cookie->errorp);
			(void) __ns_ldap_freeResult(&cookie->result);
			cookie->sdpos = cookie->sdlist;
			cookie->err_from_result = 0;
			cookie->err_rc = 0;
			cookie->new_state = NEXT_SESSION;
			break;
		case NEXT_SEARCH_DESCRIPTOR:
			/* get next search descriptor */
			if (cookie->sdpos == NULL) {
				cookie->sdpos = cookie->sdlist;
				cookie->new_state = GET_SESSION;
			} else {
				cookie->sdpos++;
				cookie->new_state = NEXT_SEARCH;
			}
			if (*cookie->sdpos == NULL)
				cookie->new_state = EXIT;
			break;
		case GET_SESSION:
			if (get_current_session(cookie) < 0)
				cookie->new_state = NEXT_SESSION;
			else
				cookie->new_state = NEXT_SEARCH;
			break;
		case NEXT_SESSION:
			if (get_next_session(cookie) < 0)
				cookie->new_state = RESTART_SESSION;
			else
				cookie->new_state = NEXT_SEARCH;
			break;
		case RESTART_SESSION:
			if (cookie->i_flags & NS_LDAP_HARD) {
				cookie->new_state = NEXT_SESSION;
				break;
			}
			(void) sprintf(errstr,
			    gettext("Session error no available conn.\n"),
			    state);
			err = strdup(errstr);
			MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, err,
			    LDAP_ERROR);
			cookie->err_rc = NS_LDAP_INTERNAL;
			cookie->errorp = *errorp;
			cookie->new_state = EXIT;
			break;
		case NEXT_SEARCH:
			/* setup referrals search if necessary */
			if (cookie->refpos) {
				if (setup_referral_search(cookie) < 0) {
					cookie->new_state = EXIT;
					break;
				}
			} else if (setup_next_search(cookie) < 0) {
				cookie->new_state = EXIT;
				break;
			}
			/* only do VLV/PAGE on scopes onelevel/subtree */
			if (paging_supported(cookie)) {
				if (cookie->use_paging &&
				    (cookie->scope != LDAP_SCOPE_BASE)) {
					cookie->index = 1;
					if (cookie->listType == VLVCTRLFLAG)
						cookie->new_state = NEXT_VLV;
					else
						cookie->new_state = NEXT_PAGE;
					break;
				}
			}
			cookie->new_state = ONE_SEARCH;
			break;
		case NEXT_VLV:
			rc = setup_vlv_params(cookie);
			if (rc != LDAP_SUCCESS) {
				cookie->err_rc = rc;
				cookie->new_state = LDAP_ERROR;
				break;
			}
			cookie->next_state = MULTI_RESULT;
			cookie->new_state = DO_SEARCH;
			break;
		case NEXT_PAGE:
			rc = setup_simplepg_params(cookie);
			if (rc != LDAP_SUCCESS) {
				cookie->err_rc = rc;
				cookie->new_state = LDAP_ERROR;
				break;
			}
			cookie->next_state = MULTI_RESULT;
			cookie->new_state = DO_SEARCH;
			break;
		case ONE_SEARCH:
			cookie->next_state = NEXT_RESULT;
			cookie->new_state = DO_SEARCH;
			break;
		case DO_SEARCH:
			cookie->entryCount = 0;
			rc = ldap_search_ext(cookie->conn->ld,
			    cookie->basedn,
			    cookie->scope,
			    cookie->filter,
			    cookie->attribute,
			    0,
			    cookie->p_serverctrls,
			    NULL,
			    &cookie->search_timeout, 0,
			    &cookie->msgId);
			if (rc != LDAP_SUCCESS) {
				if (rc == LDAP_BUSY ||
				    rc == LDAP_UNAVAILABLE ||
				    rc == LDAP_UNWILLING_TO_PERFORM ||
				    rc == LDAP_CONNECT_ERROR ||
				    rc == LDAP_SERVER_DOWN) {

					if (cookie->reinit_on_retriable_err) {
						cookie->err_rc = rc;
						cookie->new_state = REINIT;
					} else {
						cookie->new_state =
						    NEXT_SESSION;
					}

					/*
					 * If not able to reach the
					 * server, inform the ldap
					 * cache manager that the
					 * server should be removed
					 * from it's server list.
					 * Thus, the manager will not
					 * return this server on the next
					 * get-server request and will
					 * also reduce the server list
					 * refresh TTL, so that it will
					 * find out sooner when the server
					 * is up again.
					 */
					if ((rc == LDAP_CONNECT_ERROR ||
					    rc == LDAP_SERVER_DOWN) &&
					    (cookie->conn_user == NULL ||
					    cookie->conn_user->conn_mt ==
					    NULL)) {
						ret = __s_api_removeServer(
						    cookie->conn->serverAddr);
						if (ret == NS_CACHE_NOSERVER &&
						    cookie->conn_auth_type
						    == NS_LDAP_AUTH_NONE) {
							/*
							 * Couldn't remove
							 * server from server
							 * list.
							 * Exit to avoid
							 * potential infinite
							 * loop.
							 */
							cookie->err_rc = rc;
							cookie->new_state =
							    LDAP_ERROR;
						}
						if (cookie->connectionId > -1) {
							/*
							 * NS_LDAP_NEW_CONN
							 * indicates that the
							 * connection should
							 * be deleted, not
							 * kept alive
							 */
							DropConnection(
							    cookie->
							    connectionId,
							    NS_LDAP_NEW_CONN);
							cookie->connectionId =
							    -1;
						}
					} else if ((rc == LDAP_CONNECT_ERROR ||
					    rc == LDAP_SERVER_DOWN) &&
					    cookie->conn_user != NULL) {
						if (cookie->
						    reinit_on_retriable_err) {
							/*
							 * MT connection not
							 * usable, close it
							 * before REINIT.
							 * rc has already
							 * been saved in
							 * cookie->err_rc above.
							 */
							__s_api_conn_mt_close(
							    cookie->conn_user,
							    rc,
							    &cookie->errorp);
						} else {
							/*
							 * MT connection not
							 * usable, close it in
							 * the LDAP_ERROR state.
							 * A retry will be done
							 * next if allowed.
							 */
							cookie->err_rc = rc;
							cookie->new_state =
							    LDAP_ERROR;
						}
					}
					break;
				}
				cookie->err_rc = rc;
				cookie->new_state = LDAP_ERROR;
				break;
			}
			cookie->new_state = cookie->next_state;
			break;
		case NEXT_RESULT:
			/*
			 * Caller (e.g. __ns_ldap_list_batch_add)
			 * does not want to block on ldap_result().
			 * Therefore we execute ldap_result() with
			 * a zeroed timeval.
			 */
			if (cookie->no_wait == B_TRUE)
				(void) memset(&tv, 0, sizeof (tv));
			else
				tv = cookie->search_timeout;
			rc = ldap_result(cookie->conn->ld, cookie->msgId,
			    LDAP_MSG_ONE,
			    &tv,
			    &cookie->resultMsg);
			if (rc == LDAP_RES_SEARCH_RESULT) {
				cookie->new_state = END_RESULT;
				/* check and process referrals info */
				if (cookie->followRef)
					proc_result_referrals(
					    cookie);
				(void) ldap_msgfree(cookie->resultMsg);
				cookie->resultMsg = NULL;
				break;
			}
			/* handle referrals if necessary */
			if (rc == LDAP_RES_SEARCH_REFERENCE) {
				if (cookie->followRef)
					proc_search_references(cookie);
				(void) ldap_msgfree(cookie->resultMsg);
				cookie->resultMsg = NULL;
				break;
			}
			if (rc != LDAP_RES_SEARCH_ENTRY) {
				switch (rc) {
				case 0:
					if (cookie->no_wait == B_TRUE) {
						(void) ldap_msgfree(
						    cookie->resultMsg);
						cookie->resultMsg = NULL;
						return (cookie->new_state);
					}
					rc = LDAP_TIMEOUT;
					break;
				case -1:
#ifdef	__dilos__
					ldap_get_option(cookie->conn->ld,
						LDAP_OPT_ERROR_NUMBER, &rc);
#else	/* ! __dilos__ */
					rc = ldap_get_lderrno(cookie->conn->ld,
					    NULL, NULL);
#endif	/* __dilos__ */
					break;
				default:
					rc = ldap_result2error(cookie->conn->ld,
					    cookie->resultMsg, 1);
					break;
				}
				if ((rc == LDAP_TIMEOUT ||
				    rc == LDAP_SERVER_DOWN) &&
				    (cookie->conn_user == NULL ||
				    cookie->conn_user->conn_mt == NULL)) {
					if (rc == LDAP_TIMEOUT)
						(void) __s_api_removeServer(
						    cookie->conn->serverAddr);
					if (cookie->connectionId > -1) {
						DropConnection(
						    cookie->connectionId,
						    NS_LDAP_NEW_CONN);
						cookie->connectionId = -1;
					}
					cookie->err_from_result = 1;
				}
				(void) ldap_msgfree(cookie->resultMsg);
				cookie->resultMsg = NULL;
				if (rc == LDAP_BUSY ||
				    rc == LDAP_UNAVAILABLE ||
				    rc == LDAP_UNWILLING_TO_PERFORM) {
					if (cookie->reinit_on_retriable_err) {
						cookie->err_rc = rc;
						cookie->err_from_result = 1;
						cookie->new_state = REINIT;
					} else {
						cookie->new_state =
						    NEXT_SESSION;
					}
					break;
				}
				if ((rc == LDAP_CONNECT_ERROR ||
				    rc == LDAP_SERVER_DOWN) &&
				    cookie->reinit_on_retriable_err) {
					ns_ldap_error_t *errorp = NULL;
					cookie->err_rc = rc;
					cookie->err_from_result = 1;
					cookie->new_state = REINIT;
					if (cookie->conn_user != NULL)
						__s_api_conn_mt_close(
						    cookie->conn_user,
						    rc, &errorp);
					if (errorp != NULL) {
						(void) __ns_ldap_freeError(
						    &cookie->errorp);
						cookie->errorp = errorp;
					}
					break;
				}
				cookie->err_rc = rc;
				cookie->new_state = LDAP_ERROR;
				break;
			}
			/* else LDAP_RES_SEARCH_ENTRY */
			/* get account management response control */
			if (cookie->nopasswd_acct_mgmt == 1) {
				rc = ldap_get_entry_controls(cookie->conn->ld,
				    cookie->resultMsg,
				    &(cookie->resultctrl));
				if (rc != LDAP_SUCCESS) {
					cookie->new_state = LDAP_ERROR;
					cookie->err_rc = rc;
					break;
				}
			}
			rc = __s_api_getEntry(cookie);
			(void) ldap_msgfree(cookie->resultMsg);
			cookie->resultMsg = NULL;
			if (rc != NS_LDAP_SUCCESS) {
				cookie->new_state = LDAP_ERROR;
				break;
			}
			cookie->new_state = PROCESS_RESULT;
			cookie->next_state = NEXT_RESULT;
			break;
		case MULTI_RESULT:
			if (cookie->no_wait == B_TRUE)
				(void) memset(&tv, 0, sizeof (tv));
			else
				tv = cookie->search_timeout;
			rc = ldap_result(cookie->conn->ld, cookie->msgId,
			    LDAP_MSG_ONE,
			    &tv,
			    &cookie->resultMsg);
			if (rc == LDAP_RES_SEARCH_RESULT) {
				rc = ldap_result2error(cookie->conn->ld,
				    cookie->resultMsg, 0);
				if (rc == LDAP_ADMINLIMIT_EXCEEDED &&
				    cookie->listType == VLVCTRLFLAG &&
				    cookie->sortTypeTry == SSS_SINGLE_ATTR) {
					/* Try old "cn uid" server side sort */
					cookie->sortTypeTry = SSS_CN_UID_ATTRS;
					cookie->new_state = NEXT_VLV;
					(void) ldap_msgfree(cookie->resultMsg);
					cookie->resultMsg = NULL;
					break;
				}
				if (rc != LDAP_SUCCESS) {
					cookie->err_rc = rc;
					cookie->new_state = LDAP_ERROR;
					(void) ldap_msgfree(cookie->resultMsg);
					cookie->resultMsg = NULL;
					break;
				}
				cookie->new_state = multi_result(cookie);
				(void) ldap_msgfree(cookie->resultMsg);
				cookie->resultMsg = NULL;
				break;
			}
			/* handle referrals if necessary */
			if (rc == LDAP_RES_SEARCH_REFERENCE &&
			    cookie->followRef) {
				proc_search_references(cookie);
				(void) ldap_msgfree(cookie->resultMsg);
				cookie->resultMsg = NULL;
				break;
			}
			if (rc != LDAP_RES_SEARCH_ENTRY) {
				switch (rc) {
				case 0:
					if (cookie->no_wait == B_TRUE) {
						(void) ldap_msgfree(
						    cookie->resultMsg);
						cookie->resultMsg = NULL;
						return (cookie->new_state);
					}
					rc = LDAP_TIMEOUT;
					break;
				case -1:
#ifdef	__dilos__
					ldap_get_option(cookie->conn->ld,
						LDAP_OPT_ERROR_NUMBER, &rc);
#else	/* !__dilos__ */
					rc = ldap_get_lderrno(cookie->conn->ld,
					    NULL, NULL);
#endif	/* __dilos__ */
					break;
				default:
					rc = ldap_result2error(cookie->conn->ld,
					    cookie->resultMsg, 1);
					break;
				}
				if ((rc == LDAP_TIMEOUT ||
				    rc == LDAP_SERVER_DOWN) &&
				    (cookie->conn_user == NULL ||
				    cookie->conn_user->conn_mt == NULL)) {
					if (rc == LDAP_TIMEOUT)
						(void) __s_api_removeServer(
						    cookie->conn->serverAddr);
					if (cookie->connectionId > -1) {
						DropConnection(
						    cookie->connectionId,
						    NS_LDAP_NEW_CONN);
						cookie->connectionId = -1;
					}
					cookie->err_from_result = 1;
				}
				(void) ldap_msgfree(cookie->resultMsg);
				cookie->resultMsg = NULL;
				if (rc == LDAP_BUSY ||
				    rc == LDAP_UNAVAILABLE ||
				    rc == LDAP_UNWILLING_TO_PERFORM) {
					if (cookie->reinit_on_retriable_err) {
						cookie->err_rc = rc;
						cookie->err_from_result = 1;
						cookie->new_state = REINIT;
					} else {
						cookie->new_state =
						    NEXT_SESSION;
					}
					break;
				}

				if ((rc == LDAP_CONNECT_ERROR ||
				    rc == LDAP_SERVER_DOWN) &&
				    cookie->reinit_on_retriable_err) {
					ns_ldap_error_t *errorp = NULL;
					cookie->err_rc = rc;
					cookie->err_from_result = 1;
					cookie->new_state = REINIT;
					if (cookie->conn_user != NULL)
						__s_api_conn_mt_close(
						    cookie->conn_user,
						    rc, &errorp);
					if (errorp != NULL) {
						(void) __ns_ldap_freeError(
						    &cookie->errorp);
						cookie->errorp = errorp;
					}
					break;
				}
				cookie->err_rc = rc;
				cookie->new_state = LDAP_ERROR;
				break;
			}
			/* else LDAP_RES_SEARCH_ENTRY */
			cookie->entryCount++;
			rc = __s_api_getEntry(cookie);
			(void) ldap_msgfree(cookie->resultMsg);
			cookie->resultMsg = NULL;
			if (rc != NS_LDAP_SUCCESS) {
				cookie->new_state = LDAP_ERROR;
				break;
			}
			/*
			 * If VLV search was successfull save the server
			 * side sort type tried.
			 */
			if (cookie->listType == VLVCTRLFLAG)
				update_srvsidesort_type(cookie->service,
				    cookie->sortTypeTry);

			cookie->new_state = PROCESS_RESULT;
			cookie->next_state = MULTI_RESULT;
			break;
		case PROCESS_RESULT:
			/* NOTE THIS STATE MAY BE PROCESSED BY CALLER */
			if (cookie->use_usercb && cookie->callback) {
				rc = 0;
				for (nextEntry = cookie->result->entry;
				    nextEntry != NULL;
				    nextEntry = nextEntry->next) {
					rc = (*cookie->callback)(nextEntry,
					    cookie->userdata);

					if (rc == NS_LDAP_CB_DONE) {
					/* cb doesn't want any more data */
						rc = NS_LDAP_PARTIAL;
						cookie->err_rc = rc;
						break;
					} else if (rc != NS_LDAP_CB_NEXT) {
					/* invalid return code */
						rc = NS_LDAP_OP_FAILED;
						cookie->err_rc = rc;
						break;
					}
				}
				(void) __ns_ldap_freeResult(&cookie->result);
				cookie->result = NULL;
			}
			if (rc != 0) {
				cookie->new_state = EXIT;
				break;
			}
			/* NOTE PREVIOUS STATE SPECIFIES NEXT STATE */
			cookie->new_state = cookie->next_state;
			break;
		case END_PROCESS_RESULT:
			cookie->new_state = cookie->next_state;
			break;
		case END_RESULT:
			/*
			 * XXX DO WE NEED THIS CASE?
			 * if (search is complete) {
			 *	cookie->new_state = EXIT;
			 * } else
			 */
				/*
				 * entering referral mode if necessary
				 */
				if (cookie->followRef && cookie->reflist)
					cookie->new_state =
					    NEXT_REFERRAL;
				else
					cookie->new_state =
					    NEXT_SEARCH_DESCRIPTOR;
			break;
		case NEXT_REFERRAL:
			/* get next referral info */
			if (cookie->refpos == NULL)
				cookie->refpos =
				    cookie->reflist;
			else
				cookie->refpos =
				    cookie->refpos->next;
			/* check see if done with all referrals */
			if (cookie->refpos != NULL) {
				cookie->new_state =
				    GET_REFERRAL_SESSION;
			} else {
				__s_api_deleteRefInfo(cookie->reflist);
				cookie->reflist = NULL;
				cookie->new_state =
				    NEXT_SEARCH_DESCRIPTOR;
				if (cookie->conn_user != NULL)
					cookie->conn_user->referral = B_FALSE;
			}
			break;
		case GET_REFERRAL_SESSION:
			if (get_referral_session(cookie) < 0) {
				cookie->new_state = EXIT;
			} else {
				cookie->new_state = NEXT_SEARCH;
			}
			break;
		case LDAP_ERROR:
			rc_save = cookie->err_rc;
			if (cookie->err_from_result) {
				if (cookie->err_rc == LDAP_SERVER_DOWN) {
					(void) sprintf(errstr,
					    gettext("LDAP ERROR (%d): "
					    "Error occurred during"
					    " receiving results. "
					    "Connection to server lost."),
					    cookie->err_rc);
				} else if (cookie->err_rc == LDAP_TIMEOUT) {
					(void) sprintf(errstr,
					    gettext("LDAP ERROR (%d): "
					    "Error occurred during"
					    " receiving results. %s"
					    "."), cookie->err_rc,
					    ldap_err2string(
					    cookie->err_rc));
				}
			} else {
				(void) sprintf(errstr,
				    gettext("LDAP ERROR (%d): %s."),
				    cookie->err_rc,
				    ldap_err2string(cookie->err_rc));
			}
			err = strdup(errstr);
			if (cookie->err_from_result) {
				if (cookie->err_rc == LDAP_SERVER_DOWN) {
					MKERROR(LOG_INFO, *errorp,
					    cookie->err_rc, err,
					    LDAP_ERROR);
				} else {
					MKERROR(LOG_WARNING, *errorp,
					    cookie->err_rc, err,
					    LDAP_ERROR);
				}
			} else {
				MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL,
				    err, LDAP_ERROR);
			}
			cookie->err_rc = NS_LDAP_INTERNAL;
			cookie->errorp = *errorp;
			if (cookie->conn_user != NULL)  {
				if (rc_save == LDAP_SERVER_DOWN ||
				    rc_save == LDAP_CONNECT_ERROR) {
					/*
					 * MT connection is not usable,
					 * close it.
					 */
					__s_api_conn_mt_close(cookie->conn_user,
					    rc_save, &cookie->errorp);
					return (ERROR);
				}
			}
			return (ERROR);
		default:
		case ERROR:
			(void) sprintf(errstr,
			    gettext("Internal State machine exit (%d).\n"),
			    cookie->state);
			err = strdup(errstr);
			MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, err,
			    LDAP_ERROR);
			cookie->err_rc = NS_LDAP_INTERNAL;
			cookie->errorp = *errorp;
			return (ERROR);
		}

		if (cookie->conn_user != NULL &&
		    cookie->conn_user->bad_mt_conn ==  B_TRUE) {
			__s_api_conn_mt_close(cookie->conn_user, 0, NULL);
			cookie->err_rc = cookie->conn_user->ns_rc;
			cookie->errorp = cookie->conn_user->ns_error;
			cookie->conn_user->ns_error = NULL;
			return (ERROR);
		}

		if (cycle == ONE_STEP) {
			return (cookie->new_state);
		}
		cookie->state = cookie->new_state;
	}
	/*NOTREACHED*/
#if 0
	(void) sprintf(errstr,
	    gettext("Unexpected State machine error.\n"));
	err = strdup(errstr);
	MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, err, NS_LDAP_MEMORY);
	cookie->err_rc = NS_LDAP_INTERNAL;
	cookie->errorp = *errorp;
	return (ERROR);
#endif
}

/*
 * For a lookup of shadow data, if shadow update is enabled,
 * check the calling process' privilege to ensure it's
 * allowed to perform such operation.
 */
static int
check_shadow(ns_ldap_cookie_t *cookie, const char *service)
{
	char errstr[MAXERROR];
	char *err;
	boolean_t priv;
	/* caller */
	priv_set_t *ps;
	/* zone */
	priv_set_t *zs;

	/*
	 * If service is "shadow", we may need
	 * to use privilege credentials.
	 */
	if ((strcmp(service, "shadow") == 0) &&
	    __ns_ldap_is_shadow_update_enabled()) {
		/*
		 * Since we release admin credentials after
		 * connection is closed and we do not cache
		 * them, we allow any root or all zone
		 * privilege process to read shadow data.
		 */
		priv = (geteuid() == 0);
		if (!priv) {
			/* caller */
			ps = priv_allocset();

			(void) getppriv(PRIV_EFFECTIVE, ps);
			zs = priv_str_to_set("zone", ",", NULL);
			priv = priv_isequalset(ps, zs);
			priv_freeset(ps);
			priv_freeset(zs);
		}
		if (!priv) {
			(void) sprintf(errstr,
			    gettext("Permission denied"));
			err = strdup(errstr);
			if (err == NULL)
				return (NS_LDAP_MEMORY);
			MKERROR(LOG_INFO, cookie->errorp, NS_LDAP_INTERNAL, err,
			    NS_LDAP_MEMORY);
			return (NS_LDAP_INTERNAL);
		}
		cookie->i_flags |= NS_LDAP_READ_SHADOW;
		/*
		 * We do not want to reuse connection (hence
		 * keep it open) with admin credentials.
		 * If NS_LDAP_KEEP_CONN is set, reject the
		 * request.
		 */
		if (cookie->i_flags & NS_LDAP_KEEP_CONN)
			return (NS_LDAP_INVALID_PARAM);
		cookie->i_flags |= NS_LDAP_NEW_CONN;
	}

	return (NS_LDAP_SUCCESS);
}

/*
 * internal function for __ns_ldap_list
 */
static int
ldap_list(
	ns_ldap_list_batch_t *batch,
	const char *service,
	const char *filter,
	const char *sortattr,
	int (*init_filter_cb)(const ns_ldap_search_desc_t *desc,
	char **realfilter, const void *userdata),
	const char * const *attribute,
	const ns_cred_t *auth,
	const int flags,
	ns_ldap_result_t **rResult, /* return result entries */
	ns_ldap_error_t **errorp,
	int *rcp,
	int (*callback)(const ns_ldap_entry_t *entry, const void *userdata),
	const void *userdata, ns_conn_user_t *conn_user)
{
	ns_ldap_cookie_t	*cookie;
	ns_ldap_search_desc_t	**sdlist = NULL;
	ns_ldap_search_desc_t	*dptr;
	ns_ldap_error_t		*error = NULL;
	char			**dns = NULL;
	int			scope;
	int			rc;
	int			from_result;

	*errorp = NULL;
	*rResult = NULL;
	*rcp = NS_LDAP_SUCCESS;

	/*
	 * Sanity check - NS_LDAP_READ_SHADOW is for our
	 * own internal use.
	 */
	if (flags & NS_LDAP_READ_SHADOW)
		return (NS_LDAP_INVALID_PARAM);

	/* Initialize State machine cookie */
	cookie = init_search_state_machine();
	if (cookie == NULL) {
		*rcp = NS_LDAP_MEMORY;
		return (NS_LDAP_MEMORY);
	}
	cookie->conn_user = conn_user;

	/* see if need to follow referrals */
	rc = __s_api_toFollowReferrals(flags,
	    &cookie->followRef, errorp);
	if (rc != NS_LDAP_SUCCESS) {
		delete_search_cookie(cookie);
		*rcp = rc;
		return (rc);
	}

	/* get the service descriptor - or create a default one */
	rc = __s_api_get_SSD_from_SSDtoUse_service(service,
	    &sdlist, &error);
	if (rc != NS_LDAP_SUCCESS) {
		delete_search_cookie(cookie);
		*errorp = error;
		*rcp = rc;
		return (rc);
	}

	if (sdlist == NULL) {
		/* Create default service Desc */
		sdlist = (ns_ldap_search_desc_t **)calloc(2,
		    sizeof (ns_ldap_search_desc_t *));
		if (sdlist == NULL) {
			delete_search_cookie(cookie);
			cookie = NULL;
			*rcp = NS_LDAP_MEMORY;
			return (NS_LDAP_MEMORY);
		}
		dptr = (ns_ldap_search_desc_t *)
		    calloc(1, sizeof (ns_ldap_search_desc_t));
		if (dptr == NULL) {
			free(sdlist);
			delete_search_cookie(cookie);
			cookie = NULL;
			*rcp = NS_LDAP_MEMORY;
			return (NS_LDAP_MEMORY);
		}
		sdlist[0] = dptr;

		/* default base */
		rc = __s_api_getDNs(&dns, service, &cookie->errorp);
		if (rc != NS_LDAP_SUCCESS) {
			if (dns) {
				__s_api_free2dArray(dns);
				dns = NULL;
			}
			*errorp = cookie->errorp;
			cookie->errorp = NULL;
			delete_search_cookie(cookie);
			cookie = NULL;
			*rcp = rc;
			return (rc);
		}
		dptr->basedn = strdup(dns[0]);
		__s_api_free2dArray(dns);
		dns = NULL;

		/* default scope */
		scope = 0;
		rc = __s_api_getSearchScope(&scope, &cookie->errorp);
		dptr->scope = scope;
	}

	cookie->sdlist = sdlist;

	/*
	 * use VLV/PAGE control only if NS_LDAP_PAGE_CTRL is set
	 */
	if (flags & NS_LDAP_PAGE_CTRL)
		cookie->use_paging = TRUE;
	else
		cookie->use_paging = FALSE;

	/* Set up other arguments */
	cookie->userdata = userdata;
	if (init_filter_cb != NULL) {
		cookie->init_filter_cb = init_filter_cb;
		cookie->use_filtercb = 1;
	}
	if (callback != NULL) {
		cookie->callback = callback;
		cookie->use_usercb = 1;
	}

	/* check_shadow() may add extra value to cookie->i_flags */
	cookie->i_flags = flags;
	if (service) {
		cookie->service = strdup(service);
		if (cookie->service == NULL) {
			delete_search_cookie(cookie);
			cookie = NULL;
			*rcp = NS_LDAP_MEMORY;
			return (NS_LDAP_MEMORY);
		}

		/*
		 * If given, use the credential given by the caller, and
		 * skip the credential check required for shadow update.
		 */
		if (auth == NULL) {
			rc = check_shadow(cookie, service);
			if (rc != NS_LDAP_SUCCESS) {
				*errorp = cookie->errorp;
				cookie->errorp = NULL;
				delete_search_cookie(cookie);
				cookie = NULL;
				*rcp = rc;
				return (rc);
			}
		}
	}

	cookie->i_filter = strdup(filter);
	cookie->i_attr = attribute;
	cookie->i_auth = auth;
	cookie->i_sortattr = sortattr;

	if (batch != NULL) {
		cookie->batch = batch;
		cookie->reinit_on_retriable_err = B_TRUE;
		cookie->no_wait = B_TRUE;
		(void) search_state_machine(cookie, INIT, 0);
		cookie->no_wait = B_FALSE;
		rc = cookie->err_rc;

		if (rc == NS_LDAP_SUCCESS) {
			/*
			 * Here rc == NS_LDAP_SUCCESS means that the state
			 * machine init'ed successfully. The actual status
			 * of the search will be determined by
			 * __ns_ldap_list_batch_end(). Add the cookie to our
			 * batch.
			 */
			cookie->caller_result = rResult;
			cookie->caller_errorp = errorp;
			cookie->caller_rc = rcp;
			cookie->next_cookie_in_batch = batch->cookie_list;
			batch->cookie_list = cookie;
			batch->nactive++;
			return (rc);
		}
		/*
		 * If state machine init failed then copy error to the caller
		 * and delete the cookie.
		 */
	} else {
		(void) search_state_machine(cookie, INIT, 0);
	}

	/* Copy results back to user */
	rc = cookie->err_rc;
	if (rc != NS_LDAP_SUCCESS) {
		if (conn_user != NULL && conn_user->ns_error != NULL) {
			*errorp = conn_user->ns_error;
			conn_user->ns_error = NULL;
		} else {
			*errorp = cookie->errorp;
		}
	}
	*rResult = cookie->result;
	from_result = cookie->err_from_result;

	cookie->errorp = NULL;
	cookie->result = NULL;
	delete_search_cookie(cookie);
	cookie = NULL;

	if (from_result == 0 && *rResult == NULL)
		rc = NS_LDAP_NOTFOUND;
	*rcp = rc;
	return (rc);
}


/*
 * __ns_ldap_list performs one or more LDAP searches to a given
 * directory server using service search descriptors and schema
 * mapping as appropriate. The operation may be retried a
 * couple of times in error situations.
 */
int
__ns_ldap_list(
	const char *service,
	const char *filter,
	int (*init_filter_cb)(const ns_ldap_search_desc_t *desc,
	char **realfilter, const void *userdata),
	const char * const *attribute,
	const ns_cred_t *auth,
	const int flags,
	ns_ldap_result_t **rResult, /* return result entries */
	ns_ldap_error_t **errorp,
	int (*callback)(const ns_ldap_entry_t *entry, const void *userdata),
	const void *userdata)
{
	int mod_flags;
	/*
	 * Strip the NS_LDAP_PAGE_CTRL option as this interface does not
	 * support this. If you want to use this option call the API
	 * __ns_ldap_list_sort() with has the sort attribute.
	 */
	mod_flags = flags & (~NS_LDAP_PAGE_CTRL);

	return (__ns_ldap_list_sort(service, filter, NULL, init_filter_cb,
	    attribute, auth, mod_flags, rResult, errorp,
	    callback, userdata));
}

/*
 * __ns_ldap_list_sort performs one or more LDAP searches to a given
 * directory server using service search descriptors and schema
 * mapping as appropriate. The operation may be retried a
 * couple of times in error situations.
 */
int
__ns_ldap_list_sort(
	const char *service,
	const char *filter,
	const char *sortattr,
	int (*init_filter_cb)(const ns_ldap_search_desc_t *desc,
	char **realfilter, const void *userdata),
	const char * const *attribute,
	const ns_cred_t *auth,
	const int flags,
	ns_ldap_result_t **rResult, /* return result entries */
	ns_ldap_error_t **errorp,
	int (*callback)(const ns_ldap_entry_t *entry, const void *userdata),
	const void *userdata)
{
	ns_conn_user_t	*cu = NULL;
	int		try_cnt = 0;
	int		rc = NS_LDAP_SUCCESS, trc;

	for (;;) {
		if (__s_api_setup_retry_search(&cu, NS_CONN_USER_SEARCH,
		    &try_cnt, &rc, errorp) == 0)
			break;
		rc = ldap_list(NULL, service, filter, sortattr, init_filter_cb,
		    attribute, auth, flags, rResult, errorp, &trc, callback,
		    userdata, cu);
	}

	return (rc);
}

/*
 * Create and initialize batch for native LDAP lookups
 */
int
__ns_ldap_list_batch_start(ns_ldap_list_batch_t **batch)
{
	*batch = calloc(1, sizeof (ns_ldap_list_batch_t));
	if (*batch == NULL)
		return (NS_LDAP_MEMORY);
	return (NS_LDAP_SUCCESS);
}


/*
 * Add a LDAP search request to the batch.
 */
int
__ns_ldap_list_batch_add(
	ns_ldap_list_batch_t *batch,
	const char *service,
	const char *filter,
	int (*init_filter_cb)(const ns_ldap_search_desc_t *desc,
	char **realfilter, const void *userdata),
	const char * const *attribute,
	const ns_cred_t *auth,
	const int flags,
	ns_ldap_result_t **rResult, /* return result entries */
	ns_ldap_error_t **errorp,
	int *rcp,
	int (*callback)(const ns_ldap_entry_t *entry, const void *userdata),
	const void *userdata)
{
	ns_conn_user_t	*cu;
	int		rc;
	int		mod_flags;

	cu =  __s_api_conn_user_init(NS_CONN_USER_SEARCH, NULL, 0);
	if (cu == NULL) {
		if (rcp != NULL)
			*rcp = NS_LDAP_MEMORY;
		return (NS_LDAP_MEMORY);
	}

	/*
	 * Strip the NS_LDAP_PAGE_CTRL option as the batch interface does not
	 * support this.
	 */
	mod_flags = flags & (~NS_LDAP_PAGE_CTRL);

	rc = ldap_list(batch, service, filter, NULL, init_filter_cb, attribute,
	    auth, mod_flags, rResult, errorp, rcp, callback, userdata, cu);

	/*
	 * Free the conn_user if the cookie was not batched. If the cookie
	 * was batched then __ns_ldap_list_batch_end or release will free the
	 * conn_user. The batch API instructs the search_state_machine
	 * to reinit and retry (max 3 times) on retriable LDAP errors.
	 */
	if (rc != NS_LDAP_SUCCESS && cu != NULL) {
		if (cu->conn_mt != NULL)
			__s_api_conn_mt_return(cu);
		__s_api_conn_user_free(cu);
	}
	return (rc);
}


/*
 * Free batch.
 */
void
__ns_ldap_list_batch_release(ns_ldap_list_batch_t *batch)
{
	ns_ldap_cookie_t	*c, *next;

	for (c = batch->cookie_list; c != NULL; c = next) {
		next = c->next_cookie_in_batch;
		if (c->conn_user != NULL) {
			if (c->conn_user->conn_mt != NULL)
				__s_api_conn_mt_return(c->conn_user);
			__s_api_conn_user_free(c->conn_user);
			c->conn_user = NULL;
		}
		delete_search_cookie(c);
	}
	free(batch);
}

#define	LD_USING_STATE(st) \
	((st == DO_SEARCH) || (st == MULTI_RESULT) || (st == NEXT_RESULT))

/*
 * Process batch. Everytime this function is called it selects an
 * active cookie from the batch and single steps through the
 * search_state_machine for the selected cookie. If lookup associated
 * with the cookie is complete (success or error) then the cookie is
 * removed from the batch and its memory freed.
 *
 * Returns 1 (if batch still has active cookies)
 *         0 (if batch has no more active cookies)
 *        -1 (on errors, *rcp will contain the error code)
 *
 * The caller should call this function in a loop as long as it returns 1
 * to process all the requests added to the batch. The results (and errors)
 * will be available in the locations provided by the caller at the time of
 * __ns_ldap_list_batch_add().
 */
static
int
__ns_ldap_list_batch_process(ns_ldap_list_batch_t *batch, int *rcp)
{
	ns_ldap_cookie_t	*c, *ptr, **prev;
	ns_state_t		state;
	ns_ldap_error_t		*errorp = NULL;
	int			rc;

	/* Check if are already done */
	if (batch->nactive == 0)
		return (0);

	/* Get the next cookie from the batch */
	c = (batch->next_cookie == NULL) ?
	    batch->cookie_list : batch->next_cookie;

	batch->next_cookie = c->next_cookie_in_batch;

	/*
	 * Checks the status of the cookie's connection if it needs
	 * to use that connection for ldap_search_ext or ldap_result.
	 * If the connection is no longer good but worth retrying
	 * then reinit the search_state_machine for this cookie
	 * starting from the first search descriptor. REINIT will
	 * clear any leftover results if max retries have not been
	 * reached and redo the search (which may also involve
	 * following referrals again).
	 *
	 * Note that each cookie in the batch will make this
	 * determination when it reaches one of the LD_USING_STATES.
	 */
	if (LD_USING_STATE(c->new_state) && c->conn_user != NULL) {
		rc = __s_api_setup_getnext(c->conn_user, &c->err_rc, &errorp);
		if (rc == LDAP_BUSY || rc == LDAP_UNAVAILABLE ||
		    rc == LDAP_UNWILLING_TO_PERFORM) {
			if (errorp != NULL) {
				(void) __ns_ldap_freeError(&c->errorp);
				c->errorp = errorp;
			}
			c->new_state = REINIT;
		} else if (rc == LDAP_CONNECT_ERROR ||
		    rc == LDAP_SERVER_DOWN) {
			if (errorp != NULL) {
				(void) __ns_ldap_freeError(&c->errorp);
				c->errorp = errorp;
			}
			c->new_state = REINIT;
			/*
			 * MT connection is not usable,
			 * close it before REINIT.
			 */
			__s_api_conn_mt_close(
			    c->conn_user, rc, NULL);
		} else if (rc != NS_LDAP_SUCCESS) {
			if (rcp != NULL)
				*rcp = rc;
			*c->caller_result = NULL;
			*c->caller_errorp = errorp;
			*c->caller_rc = rc;
			return (-1);
		}
	}

	for (;;) {
		/* Single step through the search_state_machine */
		state = search_state_machine(c, c->new_state, ONE_STEP);
		switch (state) {
		case LDAP_ERROR:
			(void) search_state_machine(c, state, ONE_STEP);
			(void) search_state_machine(c, CLEAR_RESULTS, ONE_STEP);
			/* FALLTHROUGH */
		case ERROR:
		case EXIT:
			*c->caller_result = c->result;
			*c->caller_errorp = c->errorp;
			*c->caller_rc =
			    (c->result == NULL && c->err_from_result == 0)
			    ? NS_LDAP_NOTFOUND : c->err_rc;
			c->result = NULL;
			c->errorp = NULL;
			/* Remove the cookie from the batch */
			ptr = batch->cookie_list;
			prev = &batch->cookie_list;
			while (ptr != NULL) {
				if (ptr == c) {
					*prev = ptr->next_cookie_in_batch;
					break;
				}
				prev = &ptr->next_cookie_in_batch;
				ptr = ptr->next_cookie_in_batch;
			}
			/* Delete cookie and decrement active cookie count */
			if (c->conn_user != NULL) {
				if (c->conn_user->conn_mt != NULL)
					__s_api_conn_mt_return(c->conn_user);
				__s_api_conn_user_free(c->conn_user);
				c->conn_user = NULL;
			}
			delete_search_cookie(c);
			batch->nactive--;
			break;
		case NEXT_RESULT:
		case MULTI_RESULT:
			/*
			 * This means that search_state_machine needs to do
			 * another ldap_result() for the cookie in question.
			 * We only do at most one ldap_result() per call in
			 * this function and therefore we return. This allows
			 * the caller to process results from other cookies
			 * in the batch without getting tied up on just one
			 * cookie.
			 */
			break;
		default:
			/*
			 * This includes states that follow NEXT_RESULT or
			 * MULTI_RESULT such as PROCESS_RESULT and
			 * END_PROCESS_RESULT. We continue processing
			 * this cookie till we reach either the error, exit
			 * or the result states.
			 */
			continue;
		}
		break;
	}

	/* Return 0 if no more cookies left otherwise 1 */
	return ((batch->nactive > 0) ? 1 : 0);
}


/*
 * Process all the active cookies in the batch and when none
 * remains finalize the batch.
 */
int
__ns_ldap_list_batch_end(ns_ldap_list_batch_t *batch)
{
	int rc = NS_LDAP_SUCCESS;
	while (__ns_ldap_list_batch_process(batch, &rc) > 0)
		;
	__ns_ldap_list_batch_release(batch);
	return (rc);
}

typedef struct lookup_data {
	const char *lkd_dn;
	const char *lkd_service;
	const char *lkd_filter;
	const ns_cred_t *lkd_cred;
	ns_conn_user_t *lkd_user;
} lookup_data_t;

/*
 * This creates a service search descriptor that can be used to
 * retrieve a specific DN by using the DN as the basedn with a search
 * scope of 'base'. We don't use any service SSDs in this instance since
 * they are intended to search specific locations/subtrees and filter the
 * results, while here we are wanting to retrieve a specific entry.
 */
static int
lookup_create_ssd(lookup_data_t *dn_data, ns_ldap_search_desc_t **descpp)
{
	ns_ldap_search_desc_t *dptr;

	*descpp = NULL;

	dptr = calloc(1, sizeof (ns_ldap_search_desc_t));
	if (dptr == NULL)
		return (NS_LDAP_MEMORY);

	dptr->basedn = strdup(dn_data->lkd_dn);
	dptr->scope = NS_LDAP_SCOPE_BASE;
	dptr->filter = strdup(dn_data->lkd_filter);

	if (dptr->basedn == NULL || dptr->filter == NULL) {
		__ns_ldap_freeASearchDesc(dptr);
		return (NS_LDAP_MEMORY);
	}

	*descpp = dptr;
	return (NS_LDAP_SUCCESS);
}

static int
lookup_dn(lookup_data_t *dn_data, const char **attrs,
    ns_ldap_result_t **resultp, ns_ldap_error_t **errorp)
{
	ns_ldap_cookie_t	*cookie;
	int			rc = 0;
	int			flags = 0;

	*errorp = NULL;
	*resultp = NULL;

	if (dn_data == NULL || dn_data->lkd_dn == NULL ||
	    dn_data->lkd_dn[0] == '\0' || dn_data->lkd_filter == NULL)
		return (NS_LDAP_INVALID_PARAM);

	cookie = init_search_state_machine();
	if (cookie == NULL)
		return (NS_LDAP_MEMORY);

	rc = __s_api_toFollowReferrals(flags, &cookie->followRef, errorp);
	if (rc != NS_LDAP_SUCCESS)
		goto out;

	/* 1 for SSD, 1 for terminating NULL */
	cookie->sdlist = calloc(2, sizeof (ns_ldap_search_desc_t *));
	if (cookie->sdlist == NULL) {
		rc = NS_LDAP_MEMORY;
		goto out;
	}

	rc = lookup_create_ssd(dn_data, &cookie->sdlist[0]);
	if (rc != NS_LDAP_SUCCESS)
		goto out;

	if (dn_data->lkd_service != NULL) {
		/*
		 * If a service was specified, set that on the cookie so
		 * that search_state_machine() will properly map
		 * attributes and objectclasses.
		 */
		cookie->service = strdup(dn_data->lkd_service);
		if (cookie->service == NULL) {
			rc = NS_LDAP_MEMORY;
			goto out;
		}
	}

	cookie->i_attr = attrs;
	cookie->i_auth = dn_data->lkd_cred;
	cookie->i_flags = 0;
	cookie->i_filter = strdup(dn_data->lkd_filter);
	if (cookie->i_filter == NULL) {
		rc = NS_LDAP_MEMORY;
		goto out;
	}

	/*
	 * Actually perform the search. The return value is only used when
	 * iterating through multiple results. Since we are searching with
	 * a scope of base, we will always get at most one result entry,
	 * we ignore the return value and look at err_rc to determine if
	 * there were any errors.
	 */
	(void) search_state_machine(cookie, INIT, 0);
	rc = cookie->err_rc;

	if (rc != NS_LDAP_SUCCESS) {
		ns_conn_user_t *user = dn_data->lkd_user;

		if (user != NULL && user->ns_error != NULL) {
			*errorp = user->ns_error;
			user->ns_error = NULL;
		} else {
			*errorp = cookie->errorp;
			cookie->errorp = NULL;
		}
	} else if (cookie->result != NULL) {
		*resultp = cookie->result;
		cookie->result = NULL;
	} else {
		rc = NS_LDAP_NOTFOUND;
	}

out:
	delete_search_cookie(cookie);
	return (rc);
}

/*
 * find_domainname performs one or more LDAP searches to
 * find the value of the nisdomain attribute associated with
 * the input DN (with no retry).
 */

static int
find_domainname(const char *dn, char **domainname, const ns_cred_t *cred,
    ns_ldap_error_t **errorp, ns_conn_user_t *conn_user)
{
	lookup_data_t		ldata;
	ns_ldap_result_t	*result;
	char			**value;
	int			rc;

	*domainname = NULL;
	*errorp = NULL;

	ldata.lkd_dn = dn;
	ldata.lkd_service = NULL;
	ldata.lkd_filter = _NIS_FILTER;
	ldata.lkd_cred = cred;
	ldata.lkd_user = conn_user;

	rc = lookup_dn(&ldata, nis_domain_attrs, &result, errorp);
	if (rc != NS_LDAP_SUCCESS)
		return (rc);

	value = __ns_ldap_getAttr(result->entry, _NIS_DOMAIN);

	if (value != NULL && value[0] != NULL) {
		*domainname = strdup(value[0]);
		if (*domainname == NULL)
			rc = NS_LDAP_MEMORY;
	} else {
		rc = NS_LDAP_NOTFOUND;
	}

	(void) __ns_ldap_freeResult(&result);
	return (rc);
}

/*
 * __s_api_find_domainname performs one or more LDAP searches to
 * find the value of the nisdomain attribute associated with
 * the input DN (with retry).
 */

static int
__s_api_find_domainname(const char *dn, char **domainname,
    const ns_cred_t *cred, ns_ldap_error_t **errorp)
{
	ns_conn_user_t	*cu = NULL;
	int		try_cnt = 0;
	int		rc = NS_LDAP_SUCCESS;

	for (;;) {
		if (__s_api_setup_retry_search(&cu, NS_CONN_USER_SEARCH,
		    &try_cnt, &rc, errorp) == 0)
			break;
		rc = find_domainname(dn, domainname, cred, errorp, cu);
	}

	return (rc);
}

static int
firstEntry(
    const char *service,
    const char *filter,
    const char *sortattr,
    int (*init_filter_cb)(const ns_ldap_search_desc_t *desc,
    char **realfilter, const void *userdata),
    const char * const *attribute,
    const ns_cred_t *auth,
    const int flags,
    void **vcookie,
    ns_ldap_result_t **result,
    ns_ldap_error_t ** errorp,
    const void *userdata,
    ns_conn_user_t *conn_user)
{
	ns_ldap_cookie_t	*cookie = NULL;
	ns_ldap_error_t		*error = NULL;
	ns_state_t		state;
	ns_ldap_search_desc_t	**sdlist;
	ns_ldap_search_desc_t	*dptr;
	char			**dns = NULL;
	int			scope;
	int			rc;

	*errorp = NULL;
	*result = NULL;

	/*
	 * Sanity check - NS_LDAP_READ_SHADOW is for our
	 * own internal use.
	 */
	if (flags & NS_LDAP_READ_SHADOW)
		return (NS_LDAP_INVALID_PARAM);

	/* get the service descriptor - or create a default one */
	rc = __s_api_get_SSD_from_SSDtoUse_service(service,
	    &sdlist, &error);
	if (rc != NS_LDAP_SUCCESS) {
		*errorp = error;
		return (rc);
	}
	if (sdlist == NULL) {
		/* Create default service Desc */
		sdlist = (ns_ldap_search_desc_t **)calloc(2,
		    sizeof (ns_ldap_search_desc_t *));
		if (sdlist == NULL) {
			return (NS_LDAP_MEMORY);
		}
		dptr = (ns_ldap_search_desc_t *)
		    calloc(1, sizeof (ns_ldap_search_desc_t));
		if (dptr == NULL) {
			free(sdlist);
			return (NS_LDAP_MEMORY);
		}
		sdlist[0] = dptr;

		/* default base */
		rc = __s_api_getDNs(&dns, service, &error);
		if (rc != NS_LDAP_SUCCESS) {
			if (dns) {
				__s_api_free2dArray(dns);
				dns = NULL;
			}
			if (sdlist) {
				(void) __ns_ldap_freeSearchDescriptors(
				    &sdlist);

				sdlist = NULL;
			}
			*errorp = error;
			return (rc);
		}
		dptr->basedn = strdup(dns[0]);
		__s_api_free2dArray(dns);
		dns = NULL;

		/* default scope */
		scope = 0;
		cookie = init_search_state_machine();
		if (cookie == NULL) {
			if (sdlist) {
				(void) __ns_ldap_freeSearchDescriptors(&sdlist);
				sdlist = NULL;
			}
			return (NS_LDAP_MEMORY);
		}
		rc = __s_api_getSearchScope(&scope, &cookie->errorp);
		dptr->scope = scope;
	}

	/* Initialize State machine cookie */
	if (cookie == NULL)
		cookie = init_search_state_machine();
	if (cookie == NULL) {
		if (sdlist) {
			(void) __ns_ldap_freeSearchDescriptors(&sdlist);
			sdlist = NULL;
		}
		return (NS_LDAP_MEMORY);
	}

	/* identify self as a getent user */
	cookie->conn_user = conn_user;

	cookie->sdlist = sdlist;

	/* see if need to follow referrals */
	rc = __s_api_toFollowReferrals(flags,
	    &cookie->followRef, errorp);
	if (rc != NS_LDAP_SUCCESS) {
		delete_search_cookie(cookie);
		return (rc);
	}

	/*
	 * use VLV/PAGE control only if NS_LDAP_NO_PAGE_CTRL is not set
	 */
	if (flags & NS_LDAP_NO_PAGE_CTRL)
		cookie->use_paging = FALSE;
	else
		cookie->use_paging = TRUE;

	/* Set up other arguments */
	cookie->userdata = userdata;
	if (init_filter_cb != NULL) {
		cookie->init_filter_cb = init_filter_cb;
		cookie->use_filtercb = 1;
	}
	cookie->use_usercb = 0;
	/* check_shadow() may add extra value to cookie->i_flags */
	cookie->i_flags = flags;
	if (service) {
		cookie->service = strdup(service);
		if (cookie->service == NULL) {
			delete_search_cookie(cookie);
			return (NS_LDAP_MEMORY);
		}

		/*
		 * If given, use the credential given by the caller, and
		 * skip the credential check required for shadow update.
		 */
		if (auth == NULL) {
			rc = check_shadow(cookie, service);
			if (rc != NS_LDAP_SUCCESS) {
				*errorp = cookie->errorp;
				cookie->errorp = NULL;
				delete_search_cookie(cookie);
				cookie = NULL;
				return (rc);
			}
		}
	}

	cookie->i_filter = strdup(filter);
	cookie->i_attr = attribute;
	cookie->i_sortattr = sortattr;
	cookie->i_auth = auth;

	state = INIT;
	for (;;) {
		state = search_state_machine(cookie, state, ONE_STEP);
		switch (state) {
		case PROCESS_RESULT:
			*result = cookie->result;
			cookie->result = NULL;
			*vcookie = (void *)cookie;
			return (NS_LDAP_SUCCESS);
		case LDAP_ERROR:
			state = search_state_machine(cookie, state, ONE_STEP);
			state = search_state_machine(cookie, CLEAR_RESULTS,
			    ONE_STEP);
			/* FALLTHROUGH */
		case ERROR:
			rc = cookie->err_rc;
			if (conn_user != NULL && conn_user->ns_error != NULL) {
				*errorp = conn_user->ns_error;
				conn_user->ns_error = NULL;
			} else {
				*errorp = cookie->errorp;
				cookie->errorp = NULL;
			}
			delete_search_cookie(cookie);
			return (rc);
		case EXIT:
			rc = cookie->err_rc;
			if (rc != NS_LDAP_SUCCESS) {
				*errorp = cookie->errorp;
				cookie->errorp = NULL;
			} else {
				rc = NS_LDAP_NOTFOUND;
			}

			delete_search_cookie(cookie);
			return (rc);

		default:
			break;
		}
	}
}

int
__ns_ldap_firstEntry(
    const char *service,
    const char *filter,
    const char *vlv_sort,
    int (*init_filter_cb)(const ns_ldap_search_desc_t *desc,
    char **realfilter, const void *userdata),
    const char * const *attribute,
    const ns_cred_t *auth,
    const int flags,
    void **vcookie,
    ns_ldap_result_t **result,
    ns_ldap_error_t ** errorp,
    const void *userdata)
{
	ns_conn_user_t	*cu = NULL;
	int		try_cnt = 0;
	int		rc = NS_LDAP_SUCCESS;

	for (;;) {
		if (__s_api_setup_retry_search(&cu, NS_CONN_USER_GETENT,
		    &try_cnt, &rc, errorp) == 0)
			break;
		rc = firstEntry(service, filter, vlv_sort, init_filter_cb,
		    attribute, auth, flags, vcookie, result, errorp, userdata,
		    cu);
	}
	return (rc);
}

/*ARGSUSED2*/
int
__ns_ldap_nextEntry(void *vcookie, ns_ldap_result_t **result,
    ns_ldap_error_t ** errorp)
{
	ns_ldap_cookie_t	*cookie;
	ns_state_t		state;
	int			rc;

	cookie = (ns_ldap_cookie_t *)vcookie;
	cookie->result = NULL;
	*result = NULL;

	if (cookie->conn_user != NULL) {
		rc = __s_api_setup_getnext(cookie->conn_user,
		    &cookie->err_rc, errorp);
		if (rc != NS_LDAP_SUCCESS)
			return (rc);
	}

	state = END_PROCESS_RESULT;
	for (;;) {
		state = search_state_machine(cookie, state, ONE_STEP);
		switch (state) {
		case PROCESS_RESULT:
			*result = cookie->result;
			cookie->result = NULL;
			return (NS_LDAP_SUCCESS);
		case LDAP_ERROR:
			state = search_state_machine(cookie, state, ONE_STEP);
			state = search_state_machine(cookie, CLEAR_RESULTS,
			    ONE_STEP);
			/* FALLTHROUGH */
		case ERROR:
			rc = cookie->err_rc;
			*errorp = cookie->errorp;
			cookie->errorp = NULL;
			return (rc);
		case EXIT:
			return (NS_LDAP_SUCCESS);
		}
	}
}

int
__ns_ldap_endEntry(
	void **vcookie,
	ns_ldap_error_t ** errorp)
{
	ns_ldap_cookie_t	*cookie;
	int			rc;

	if (*vcookie == NULL)
		return (NS_LDAP_INVALID_PARAM);

	cookie = (ns_ldap_cookie_t *)(*vcookie);
	cookie->result = NULL;

	/* Complete search */
	rc = search_state_machine(cookie, CLEAR_RESULTS, 0);

	/* Copy results back to user */
	rc = cookie->err_rc;
	if (rc != NS_LDAP_SUCCESS)
		*errorp = cookie->errorp;

	cookie->errorp = NULL;
	if (cookie->conn_user != NULL) {
		if (cookie->conn_user->conn_mt != NULL)
			__s_api_conn_mt_return(cookie->conn_user);
		__s_api_conn_user_free(cookie->conn_user);
	}
	delete_search_cookie(cookie);
	cookie = NULL;
	*vcookie = NULL;

	return (rc);
}


int
__ns_ldap_freeResult(ns_ldap_result_t **result)
{

	ns_ldap_entry_t	*curEntry = NULL;
	ns_ldap_entry_t	*delEntry = NULL;
	int		i;
	ns_ldap_result_t	*res = *result;

#ifdef DEBUG
	(void) fprintf(stderr, "__ns_ldap_freeResult START\n");
#endif
	if (res == NULL)
		return (NS_LDAP_INVALID_PARAM);

	if (res->entry != NULL)
		curEntry = res->entry;

	for (i = 0; i < res->entries_count; i++) {
		if (curEntry != NULL) {
			delEntry = curEntry;
			curEntry = curEntry->next;
			__ns_ldap_freeEntry(delEntry);
		}
	}

	free(res);
	*result = NULL;
	return (NS_LDAP_SUCCESS);
}

int
__ns_ldap_auth(const ns_cred_t *auth, const int flags, ns_ldap_error_t **errorp,
    LDAPControl **serverctrls __unused, LDAPControl **clientctrls __unused)
{

	ConnectionID	connectionId = -1;
	Connection	*conp;
	int		rc = 0;
	int		do_not_fail_if_new_pwd_reqd = 0;
	int		nopasswd_acct_mgmt = 0;
	ns_conn_user_t	*conn_user;


#ifdef DEBUG
	(void) fprintf(stderr, "__ns_ldap_auth START\n");
#endif

	*errorp = NULL;
	if (auth == NULL)
		return (NS_LDAP_INVALID_PARAM);

	conn_user = __s_api_conn_user_init(NS_CONN_USER_AUTH,
	    NULL, B_FALSE);

	rc = __s_api_getConnection(NULL, flags | NS_LDAP_NEW_CONN,
	    auth, &connectionId, &conp, errorp,
	    do_not_fail_if_new_pwd_reqd, nopasswd_acct_mgmt,
	    conn_user);

	if (conn_user != NULL)
		__s_api_conn_user_free(conn_user);

	if (rc == NS_LDAP_OP_FAILED && *errorp)
		(void) __ns_ldap_freeError(errorp);

	if (connectionId > -1)
		DropConnection(connectionId, flags);
	return (rc);
}

char **
__ns_ldap_getAttr(const ns_ldap_entry_t *entry, const char *attrname)
{
	int	i;

	if (entry == NULL)
		return (NULL);
	for (i = 0; i < entry->attr_count; i++) {
		if (strcasecmp(entry->attr_pair[i]->attrname, attrname) == 0)
			return (entry->attr_pair[i]->attrvalue);
	}
	return (NULL);
}

ns_ldap_attr_t *
__ns_ldap_getAttrStruct(const ns_ldap_entry_t *entry, const char *attrname)
{
	int	i;

	if (entry == NULL)
		return (NULL);
	for (i = 0; i < entry->attr_count; i++) {
		if (strcasecmp(entry->attr_pair[i]->attrname, attrname) == 0)
			return (entry->attr_pair[i]);
	}
	return (NULL);
}


int
__ns_ldap_uid2dn(const char *uid, char **userDN, const ns_cred_t *cred,
    ns_ldap_error_t **errorp)
{
	ns_ldap_result_t	*result = NULL;
	char		*filter, *userdata;
	char		errstr[MAXERROR];
	char		**value;
	int		rc = 0;
	int		i;
	size_t		len;

	*errorp = NULL;
	*userDN = NULL;
	if ((uid == NULL) || (uid[0] == '\0'))
		return (NS_LDAP_INVALID_PARAM);

	for (i = 0; uid[i] != '\0'; i++) {
		if (uid[i] == '=') {
			*userDN = strdup(uid);
			return (NS_LDAP_SUCCESS);
		}
	}
	for (i = 0; (uid[i] != '\0') && isdigit(uid[i]); i++)
		;
	if (uid[i] == '\0') {
		len = strlen(UIDNUMFILTER) + strlen(uid) + 1;
		filter = malloc(len);
		if (filter == NULL) {
			*userDN = NULL;
			return (NS_LDAP_MEMORY);
		}
		(void) snprintf(filter, len, UIDNUMFILTER, uid);

		len = strlen(UIDNUMFILTER_SSD) + strlen(uid) + 1;
		userdata = malloc(len);
		if (userdata == NULL) {
			*userDN = NULL;
			free(filter);
			return (NS_LDAP_MEMORY);
		}
		(void) snprintf(userdata, len, UIDNUMFILTER_SSD, uid);
	} else {
		len = strlen(UIDFILTER) + strlen(uid) + 1;
		filter = malloc(len);
		if (filter == NULL) {
			*userDN = NULL;
			return (NS_LDAP_MEMORY);
		}
		(void) snprintf(filter, len, UIDFILTER, uid);

		len = strlen(UIDFILTER_SSD) + strlen(uid) + 1;
		userdata = malloc(len);
		if (userdata == NULL) {
			*userDN = NULL;
			free(filter);
			return (NS_LDAP_MEMORY);
		}
		(void) snprintf(userdata, len, UIDFILTER_SSD, uid);
	}

	/*
	 * we want to retrieve the DN as it appears in LDAP
	 * hence the use of NS_LDAP_NOT_CVT_DN in flags
	 */
	rc = __ns_ldap_list("passwd", filter,
	    __s_api_merge_SSD_filter,
	    NULL, cred, NS_LDAP_NOT_CVT_DN,
	    &result, errorp, NULL,
	    userdata);
	free(filter);
	filter = NULL;
	free(userdata);
	userdata = NULL;
	if (rc != NS_LDAP_SUCCESS) {
		if (result) {
			(void) __ns_ldap_freeResult(&result);
			result = NULL;
		}
		return (rc);
	}
	if (result->entries_count > 1) {
		(void) __ns_ldap_freeResult(&result);
		result = NULL;
		*userDN = NULL;
		(void) sprintf(errstr,
		    gettext("Too many entries are returned for %s"), uid);
		MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, strdup(errstr),
		    NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	value = __ns_ldap_getAttr(result->entry, "dn");
	*userDN = strdup(value[0]);
	(void) __ns_ldap_freeResult(&result);
	result = NULL;
	return (NS_LDAP_SUCCESS);
}

#define	_P_UID	"uid"
static const char *dn2uid_attrs[] = {
	_P_CN,
	_P_UID,
	(char *)NULL
};

int
__ns_ldap_dn2uid(const char *dn, char **userIDp, const ns_cred_t *cred,
    ns_ldap_error_t **errorp)
{
	lookup_data_t		ldata;
	ns_ldap_result_t	*result;
	char			**value;
	int			rc;

	*errorp = NULL;
	*userIDp = NULL;
	if ((dn == NULL) || (dn[0] == '\0'))
		return (NS_LDAP_INVALID_PARAM);

	/*
	 * Many LDAP servers do not support using the dn in a search
	 * filter. As a result, we unfortunately cannot  use __ns_ldap_list()
	 * to lookup the DN. Instead we perform a search with the baseDN
	 * being the DN we are looking for with a scope of 'base' to
	 * return the entry, as this should be supported by all LDAP servers.
	 */
	ldata.lkd_dn = dn;

	/*
	 * Since we are looking up a user account by its DN, use the attribute
	 * and objectclass mappings (if present) for the passwd service.
	 */
	ldata.lkd_service = "passwd";
	ldata.lkd_filter = UIDDNFILTER;
	ldata.lkd_cred = cred;
	ldata.lkd_user = NULL;

	rc = lookup_dn(&ldata, dn2uid_attrs, &result, errorp);
	if (rc != NS_LDAP_SUCCESS)
		return (rc);

	value = __ns_ldap_getAttr(result->entry, _P_UID);
	if (value != NULL && value[0] != NULL) {
		*userIDp = strdup(value[0]);
		if (*userIDp == NULL)
			rc = NS_LDAP_MEMORY;
	} else {
		rc = NS_LDAP_NOTFOUND;
	}

	(void) __ns_ldap_freeResult(&result);
	return (rc);
}

int
__ns_ldap_host2dn(const char *host, const char *domain, char **hostDN,
    const ns_cred_t *cred, ns_ldap_error_t **errorp)
{
	ns_ldap_result_t	*result = NULL;
	char		*filter, *userdata;
	char		errstr[MAXERROR];
	char		**value;
	int		rc;
	size_t		len;

	/*
	 * XXX
	 * the domain parameter needs to be used in case domain is not local,
	 * if this routine is to support multi domain setups, it needs lots
	 * of work...
	 */
	*errorp = NULL;
	*hostDN = NULL;
	if ((host == NULL) || (host[0] == '\0'))
		return (NS_LDAP_INVALID_PARAM);

	len = strlen(HOSTFILTER) + strlen(host) + 1;
	filter = malloc(len);
	if (filter == NULL) {
		return (NS_LDAP_MEMORY);
	}
	(void) snprintf(filter,	len, HOSTFILTER, host);

	len = strlen(HOSTFILTER_SSD) + strlen(host) + 1;
	userdata = malloc(len);
	if (userdata == NULL) {
		free(filter);
		return (NS_LDAP_MEMORY);
	}
	(void) snprintf(userdata, len, HOSTFILTER_SSD, host);

	/*
	 * we want to retrieve the DN as it appears in LDAP
	 * hence the use of NS_LDAP_NOT_CVT_DN in flags
	 */
	rc = __ns_ldap_list("hosts", filter,
	    __s_api_merge_SSD_filter,
	    NULL, cred, NS_LDAP_NOT_CVT_DN, &result,
	    errorp, NULL,
	    userdata);
	free(filter);
	filter = NULL;
	free(userdata);
	userdata = NULL;
	if (rc != NS_LDAP_SUCCESS) {
		if (result) {
			(void) __ns_ldap_freeResult(&result);
			result = NULL;
		}
		return (rc);
	}

	if (result->entries_count > 1) {
		(void) __ns_ldap_freeResult(&result);
		result = NULL;
		*hostDN = NULL;
		(void) sprintf(errstr,
		    gettext("Too many entries are returned for %s"), host);
		MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, strdup(errstr),
		    NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	value = __ns_ldap_getAttr(result->entry, "dn");
	*hostDN = strdup(value[0]);
	(void) __ns_ldap_freeResult(&result);
	result = NULL;
	return (NS_LDAP_SUCCESS);
}

int
__ns_ldap_dn2domain(const char *dn, char **domain, const ns_cred_t *cred,
    ns_ldap_error_t **errorp)
{
	int		rc, pnum, i, j, len = 0;
	char		*newdn, **rdns = NULL;
	char		**dns, *dn1;

	*errorp = NULL;

	if (domain == NULL)
		return (NS_LDAP_INVALID_PARAM);
	else
		*domain = NULL;

	if ((dn == NULL) || (dn[0] == '\0'))
		return (NS_LDAP_INVALID_PARAM);

	/*
	 * break dn into rdns
	 */
	dn1 = strdup(dn);
	if (dn1 == NULL)
		return (NS_LDAP_MEMORY);
	rdns = ldap_explode_dn(dn1, 0);
	free(dn1);
	if (rdns == NULL || *rdns == NULL)
		return (NS_LDAP_INVALID_PARAM);

	for (i = 0; rdns[i]; i++)
		len += strlen(rdns[i]) + 1;
	pnum = i;

	newdn = (char *)malloc(len + 1);
	dns = (char **)calloc(pnum, sizeof (char *));
	if (newdn == NULL || dns == NULL) {
		if (newdn)
			free(newdn);
		ldap_value_free(rdns);
		return (NS_LDAP_MEMORY);
	}

	/* construct a semi-normalized dn, newdn */
	*newdn = '\0';
	for (i = 0; rdns[i]; i++) {
		dns[i] = newdn + strlen(newdn);
		(void) strcat(newdn,
		    __s_api_remove_rdn_space(rdns[i]));
		(void) strcat(newdn, ",");
	}
	/* remove the last ',' */
	newdn[strlen(newdn) - 1] = '\0';
	ldap_value_free(rdns);

	/*
	 * loop and find the domain name associated with newdn,
	 * removing rdn one by one from left to right
	 */
	for (i = 0; i < pnum; i++) {

		if (*errorp)
			(void) __ns_ldap_freeError(errorp);

		/*
		 *  try cache manager first
		 */
		rc = __s_api_get_cachemgr_data(NS_CACHE_DN2DOMAIN,
		    dns[i], domain);
		if (rc != NS_LDAP_SUCCESS) {
			/*
			 *  try ldap server second
			 */
			rc = __s_api_find_domainname(dns[i], domain,
			    cred, errorp);
		} else {
			/*
			 * skip the last one,
			 * since it is already cached by ldap_cachemgr
			 */
			i--;
		}
		if (rc == NS_LDAP_SUCCESS) {
			if (__s_api_nscd_proc()) {
				/*
				 * If it's nscd, ask cache manager to save the
				 * dn to domain mapping(s)
				 */
				for (j = 0; j <= i; j++) {
					(void) __s_api_set_cachemgr_data(
					    NS_CACHE_DN2DOMAIN,
					    dns[j],
					    *domain);
				}
			}
			break;
		}
	}

	free(dns);
	free(newdn);
	if (rc != NS_LDAP_SUCCESS)
		rc = NS_LDAP_NOTFOUND;
	return (rc);
}

int
__ns_ldap_getServiceAuthMethods(const char *service, ns_auth_t ***auth,
    ns_ldap_error_t **errorp)
{
	char		errstr[MAXERROR];
	int		rc, i;
	boolean_t	done = B_FALSE;
	int		slen;
	void		**param;
	char		**sam, *srv, *send;
	ns_auth_t	**authpp = NULL, *ap;
	int		cnt, max;
	ns_config_t	*cfg;
	ns_ldap_error_t	*error = NULL;

	if (errorp == NULL)
		return (NS_LDAP_INVALID_PARAM);
	*errorp = NULL;

	if ((service == NULL) || (service[0] == '\0') ||
	    (auth == NULL))
		return (NS_LDAP_INVALID_PARAM);

	*auth = NULL;
	rc = __ns_ldap_getParam(NS_LDAP_SERVICE_AUTH_METHOD_P, &param, &error);
	if (rc != NS_LDAP_SUCCESS || param == NULL) {
		*errorp = error;
		return (rc);
	}
	sam = (char **)param;

	cfg = __s_api_get_default_config();
	cnt = 0;

	slen = strlen(service);

	for (; *sam; sam++) {
		srv = *sam;
		if (strncasecmp(service, srv, slen) != 0)
			continue;
		srv += slen;
		if (*srv != COLONTOK)
			continue;
		send = srv;
		srv++;
		for (max = 1; (send = strchr(++send, SEMITOK)) != NULL; max++)
			;
		authpp = (ns_auth_t **)calloc(++max, sizeof (ns_auth_t *));
		if (authpp == NULL) {
			(void) __ns_ldap_freeParam(&param);
			__s_api_release_config(cfg);
			return (NS_LDAP_MEMORY);
		}
		while (!done) {
			send = strchr(srv, SEMITOK);
			if (send != NULL) {
				*send = '\0';
				send++;
			}
			i = __s_get_enum_value(cfg, srv, NS_LDAP_AUTH_P);
			if (i == -1) {
				(void) __ns_ldap_freeParam(&param);
				(void) sprintf(errstr,
				gettext("Unsupported "
				    "serviceAuthenticationMethod: %s.\n"), srv);
				MKERROR(LOG_WARNING, *errorp, NS_CONFIG_SYNTAX,
				    strdup(errstr), NS_LDAP_MEMORY);
				__s_api_release_config(cfg);
				return (NS_LDAP_CONFIG);
			}
			ap = __s_api_AuthEnumtoStruct((EnumAuthType_t)i);
			if (ap == NULL) {
				(void) __ns_ldap_freeParam(&param);
				__s_api_release_config(cfg);
				return (NS_LDAP_MEMORY);
			}
			authpp[cnt++] = ap;
			if (send == NULL)
				done = B_TRUE;
			else
				srv = send;
		}
	}

	*auth = authpp;
	(void) __ns_ldap_freeParam(&param);
	__s_api_release_config(cfg);
	return (NS_LDAP_SUCCESS);
}

/*
 * This routine is called when certain scenario occurs
 * e.g.
 * service == auto_home
 * SSD = automount: ou = mytest,
 * NS_LDAP_MAPATTRIBUTE= auto_home: automountMapName=AAA
 * NS_LDAP_OBJECTCLASSMAP= auto_home:automountMap=MynisMap
 * NS_LDAP_OBJECTCLASSMAP= auto_home:automount=MynisObject
 *
 * The automountMapName is prepended implicitely but is mapped
 * to AAA. So dn could appers as
 * dn: AAA=auto_home,ou=bar,dc=foo,dc=com
 * dn: automountKey=user_01,AAA=auto_home,ou=bar,dc=foo,dc=com
 * dn: automountKey=user_02,AAA=auto_home,ou=bar,dc=foo,dc=com
 * in the directory.
 * This function is called to covert the mapped attr back to
 * orig attr when the entries are searched and returned
 */

int
__s_api_convert_automountmapname(const char *service, char **dn,
    ns_ldap_error_t **errp)
{

	char	**mapping = NULL;
	char	*mapped_attr = NULL;
	char	*automountmapname = "automountMapName";
	char	*buffer = NULL;
	int	rc = NS_LDAP_SUCCESS;
	char	errstr[MAXERROR];

	/*
	 * dn is an input/out parameter, check it first
	 */

	if (service == NULL || dn == NULL || *dn == NULL)
		return (NS_LDAP_INVALID_PARAM);

	/*
	 * Check to see if there is a mapped attribute for auto_xxx
	 */

	mapping = __ns_ldap_getMappedAttributes(service, automountmapname);

	/*
	 * if no mapped attribute for auto_xxx, try automount
	 */

	if (mapping == NULL) {
		mapping = __ns_ldap_getMappedAttributes(
		    "automount", automountmapname);
	}

	/*
	 * if no mapped attribute is found, return SUCCESS (no op)
	 */

	if (mapping == NULL)
		return (NS_LDAP_SUCCESS);

	/*
	 * if the mapped attribute is found and attr is not empty,
	 * copy it
	 */

	if (mapping[0] != NULL) {
		mapped_attr = strdup(mapping[0]);
		__s_api_free2dArray(mapping);
		if (mapped_attr == NULL) {
			return (NS_LDAP_MEMORY);
		}
	} else {
		__s_api_free2dArray(mapping);

		(void) snprintf(errstr, (2 * MAXERROR),
		    gettext("Attribute nisMapName is mapped to an "
		    "empty string.\n"));

		MKERROR(LOG_ERR, *errp, NS_CONFIG_SYNTAX,
		    strdup(errstr), NS_LDAP_MEMORY);

		return (NS_LDAP_CONFIG);
	}

	/*
	 * Locate the mapped attribute in the dn
	 * and replace it if it exists
	 */

	rc = __s_api_replace_mapped_attr_in_dn(
	    (const char *) automountmapname, (const char *) mapped_attr,
	    (const char *) *dn, &buffer);

	/* clean up */

	free(mapped_attr);

	/*
	 * If mapped attr is found(buffer != NULL)
	 *	a new dn is returned
	 * If no mapped attribute is in dn,
	 *	return NS_LDAP_SUCCESS (no op)
	 * If no memory,
	 *	return NS_LDAP_MEMORY (no op)
	 */

	if (buffer != NULL) {
		free(*dn);
		*dn = buffer;
	}

	return (rc);
}

/*
 * If the mapped attr is found in the dn,
 *	return NS_LDAP_SUCCESS and a new_dn.
 * If no mapped attr is found,
 *	return NS_LDAP_SUCCESS and *new_dn == NULL
 * If there is not enough memory,
 *	return NS_LDAP_MEMORY and *new_dn == NULL
 */

int
__s_api_replace_mapped_attr_in_dn(const char *orig_attr,
    const char *mapped_attr, const char *dn, char **new_dn)
{

	char	**dnArray = NULL;
	char	*cur = NULL, *start = NULL;
	int	i = 0;
	boolean_t found = B_FALSE;
	int	len = 0, orig_len = 0, mapped_len = 0;
	int	dn_len = 0, tmp_len = 0;

	*new_dn = NULL;

	/*
	 * seperate dn into individual componets
	 * e.g.
	 * "automountKey=user_01" , "automountMapName_test=auto_home", ...
	 */
	dnArray = ldap_explode_dn(dn, 0);

	/*
	 * This will find "mapped attr=value" in dn.
	 * It won't find match if mapped attr appears
	 * in the value.
	 */
	for (i = 0; dnArray[i] != NULL; i++) {
		/*
		 * This function is called when reading from
		 * the directory so assume each component has "=".
		 * Any ill formatted dn should be rejected
		 * before adding to the directory
		 */
		cur = strchr(dnArray[i], '=');
		*cur = '\0';
		if (strcasecmp(mapped_attr, dnArray[i]) == 0)
			found = B_TRUE;
		*cur = '=';
		if (found)
			break;
	}

	if (!found) {
		__s_api_free2dArray(dnArray);
		*new_dn = NULL;
		return (NS_LDAP_SUCCESS);
	}
	/*
	 * The new length is *dn length + (difference between
	 * orig attr and mapped attr) + 1 ;
	 * e.g.
	 * automountKey=aa,automountMapName_test=auto_home,dc=foo,dc=com
	 * ==>
	 * automountKey=aa,automountMapName=auto_home,dc=foo,dc=com
	 */
	mapped_len = strlen(mapped_attr);
	orig_len = strlen(orig_attr);
	dn_len = strlen(dn);
	len = dn_len + orig_len - mapped_len + 1;
	*new_dn = (char *)calloc(1, len);
	if (*new_dn == NULL) {
		__s_api_free2dArray(dnArray);
		return (NS_LDAP_MEMORY);
	}

	/*
	 * Locate the mapped attr in the dn.
	 * Use dnArray[i] instead of mapped_attr
	 * because mapped_attr could appear in
	 * the value
	 */

	cur = strstr(dn, dnArray[i]);
	__s_api_free2dArray(dnArray);
	/* copy the portion before mapped attr in dn  */
	start = *new_dn;
	tmp_len = cur - dn;
	(void) memcpy(start, dn, tmp_len);

	/*
	 * Copy the orig_attr. e.g. automountMapName
	 * This replaces mapped attr with orig attr
	 */
	start = start + (cur - dn); /* move cursor in buffer */
	(void) memcpy(start, orig_attr, orig_len);

	/*
	 * Copy the portion after mapped attr in dn
	 */
	cur = cur + mapped_len; /* move cursor in  dn  */
	start = start + orig_len; /* move cursor in buffer */
	(void) strcpy(start, cur);

	return (NS_LDAP_SUCCESS);
}

/*
 * Validate Filter functions
 */

/* ***** Start of modified libldap.so.5 filter parser ***** */

/* filter parsing routine forward references */
static int adj_filter_list(char *str);
static int adj_simple_filter(char *str);
static int unescape_filterval(char *val);
static int hexchar2int(char c);
static int adj_substring_filter(char *val);


/*
 * assumes string manipulation is in-line
 * and all strings are sufficient in size
 * return value is the position after 'c'
 */

static char *
resync_str(char *str, char *next, char c)
{
	char	*ret;

	ret = str + strlen(str);
	*next = c;
	if (ret == next)
		return (ret);
	(void) strcat(str, next);
	return (ret);
}

static char *
find_right_paren(char *s)
{
	int balance;
	boolean_t escape;

	balance = 1;
	escape = B_FALSE;
	while (*s && balance) {
		if (escape == B_FALSE) {
			if (*s == '(')
				balance++;
			else if (*s == ')')
				balance--;
		}
		if (*s == '\\' && !escape)
			escape = B_TRUE;
		else
			escape = B_FALSE;
		if (balance)
			s++;
	}

	return (*s ? s : NULL);
}

static char *
adj_complex_filter(char	*str)
{
	char	*next;

	/*
	 * We have (x(filter)...) with str sitting on
	 * the x.  We have to find the paren matching
	 * the one before the x and put the intervening
	 * filters by calling adj_filter_list().
	 */

	str++;
	if ((next = find_right_paren(str)) == NULL)
		return (NULL);

	*next = '\0';
	if (adj_filter_list(str) == -1)
		return (NULL);
	next = resync_str(str, next, ')');
	next++;

	return (next);
}

static int
adj_filter(char *str)
{
	char *next;
	int parens, balance;
	boolean_t escape;
	char *np, *cp,  *dp;

	parens = 0;
	while (*str) {
		switch (*str) {
		case '(':
			str++;
			parens++;
			switch (*str) {
			case '&':
				if ((str = adj_complex_filter(str)) == NULL)
					return (-1);

				parens--;
				break;

			case '|':
				if ((str = adj_complex_filter(str)) == NULL)
					return (-1);

				parens--;
				break;

			case '!':
				if ((str = adj_complex_filter(str)) == NULL)
					return (-1);

				parens--;
				break;

			case '(':
				/* illegal ((case - generated by conversion */

				/* find missing close) */
				np = find_right_paren(str+1);

				/* error if not found */
				if (np == NULL)
					return (-1);

				/* remove redundant (and) */
				for (dp = str, cp = str+1; cp < np; ) {
					*dp++ = *cp++;
				}
				cp++;
				while (*cp)
					*dp++ = *cp++;
				*dp = '\0';

				/* re-start test at original ( */
				parens--;
				str--;
				break;

			default:
				balance = 1;
				escape = B_FALSE;
				next = str;
				while (*next && balance) {
					if (escape == B_FALSE) {
						if (*next == '(')
							balance++;
						else if (*next == ')')
							balance--;
					}
					if (*next == '\\' && !escape)
						escape = B_TRUE;
					else
						escape = B_FALSE;
					if (balance)
						next++;
				}
				if (balance != 0)
					return (-1);

				*next = '\0';
				if (adj_simple_filter(str) == -1) {
					return (-1);
				}
				next = resync_str(str, next, ')');
				next++;
				str = next;
				parens--;
				break;
			}
			break;

		case ')':
			str++;
			parens--;
			break;

		case ' ':
			str++;
			break;

		default:	/* assume it's a simple type=value filter */
			next = strchr(str, '\0');
			if (adj_simple_filter(str) == -1) {
				return (-1);
			}
			str = next;
			break;
		}
	}

	return (parens ? -1 : 0);
}


/*
 * Put a list of filters like this "(filter1)(filter2)..."
 */

static int
adj_filter_list(char *str)
{
	char	*next;
	char	save;

	while (*str) {
		while (*str && isspace(*str))
			str++;
		if (*str == '\0')
			break;

		if ((next = find_right_paren(str + 1)) == NULL)
			return (-1);
		save = *++next;

		/* now we have "(filter)" with str pointing to it */
		*next = '\0';
		if (adj_filter(str) == -1)
			return (-1);
		next = resync_str(str, next, save);

		str = next;
	}

	return (0);
}


/*
 * is_valid_attr - returns 1 if a is a syntactically valid left-hand side
 * of a filter expression, 0 otherwise.  A valid string may contain only
 * letters, numbers, hyphens, semi-colons, colons and periods. examples:
 *	cn
 *	cn;lang-fr
 *	1.2.3.4;binary;dynamic
 *	mail;dynamic
 *	cn:dn:1.2.3.4
 *
 * For compatibility with older servers, we also allow underscores in
 * attribute types, even through they are not allowed by the LDAPv3 RFCs.
 */
static int
is_valid_attr(char *a)
{
	for (; *a; a++) {
		if (!isascii(*a)) {
			return (0);
		} else if (!isalnum(*a)) {
			switch (*a) {
			case '-':
			case '.':
			case ';':
			case ':':
			case '_':
				break; /* valid */
			default:
				return (0);
			}
		}
	}
	return (1);
}

static char *
find_star(char *s)
{
	for (; *s; ++s) {
		switch (*s) {
		case '*':
			return (s);
		case '\\':
			++s;
			if (hexchar2int(s[0]) >= 0 && hexchar2int(s[1]) >= 0)
				++s;
		default:
			break;
		}
	}
	return (NULL);
}

static int
adj_simple_filter(char *str)
{
	char		*s, *s2, *s3, filterop;
	char		*value;
	int		ftype = 0;
	int		rc;

	rc = -1;	/* pessimistic */

	if ((str = strdup(str)) == NULL) {
		return (rc);
	}

	if ((s = strchr(str, '=')) == NULL) {
		goto free_and_return;
	}
	value = s + 1;
	*s-- = '\0';
	filterop = *s;
	if (filterop == '<' || filterop == '>' || filterop == '~' ||
	    filterop == ':') {
		*s = '\0';
	}

	if (!is_valid_attr(str)) {
		goto free_and_return;
	}

	switch (filterop) {
	case '<': /* LDAP_FILTER_LE */
	case '>': /* LDAP_FILTER_GE */
	case '~': /* LDAP_FILTER_APPROX */
		break;
	case ':':	/* extended filter - v3 only */
		/*
		 * extended filter looks like this:
		 *
		 *	[type][':dn'][':'oid]':='value
		 *
		 * where one of type or :oid is required.
		 *
		 */
		s2 = s3 = NULL;
		if ((s2 = strrchr(str, ':')) == NULL) {
			goto free_and_return;
		}
		if (strcasecmp(s2, ":dn") == 0) {
			*s2 = '\0';
		} else {
			*s2 = '\0';
			if ((s3 = strrchr(str, ':')) != NULL) {
				if (strcasecmp(s3, ":dn") != 0) {
					goto free_and_return;
				}
				*s3 = '\0';
			}
		}
		if (unescape_filterval(value) < 0) {
			goto free_and_return;
		}
		rc = 0;
		goto free_and_return;
		/* break; */
	default:
		if (find_star(value) == NULL) {
			ftype = 0; /* LDAP_FILTER_EQUALITY */
		} else if (strcmp(value, "*") == 0) {
			ftype = 1; /* LDAP_FILTER_PRESENT */
		} else {
			rc = adj_substring_filter(value);
			goto free_and_return;
		}
		break;
	}

	if (ftype != 0) {	/* == LDAP_FILTER_PRESENT */
		rc = 0;
	} else if (unescape_filterval(value) >= 0) {
		rc = 0;
	}
	if (rc != -1) {
		rc = 0;
	}

free_and_return:
	free(str);
	return (rc);
}


/*
 * Check in place both LDAPv2 (RFC-1960) and LDAPv3 (hexadecimal) escape
 * sequences within the null-terminated string 'val'.
 *
 * If 'val' contains invalid escape sequences we return -1.
 * Otherwise return 1
 */
static int
unescape_filterval(char *val)
{
	boolean_t escape, firstdigit;
	char *s;

	firstdigit = B_FALSE;
	escape = B_FALSE;
	for (s = val; *s; s++) {
		if (escape) {
			/*
			 * first try LDAPv3 escape (hexadecimal) sequence
			 */
			if (hexchar2int(*s) < 0) {
				if (firstdigit) {
					/*
					 * LDAPv2 (RFC1960) escape sequence
					 */
					escape = B_FALSE;
				} else {
					return (-1);
				}
			}
			if (firstdigit) {
				firstdigit = B_FALSE;
			} else {
				escape = B_FALSE;
			}

		} else if (*s != '\\') {
			escape = B_FALSE;

		} else {
			escape = B_TRUE;
			firstdigit = B_TRUE;
		}
	}

	return (1);
}


/*
 * convert character 'c' that represents a hexadecimal digit to an integer.
 * if 'c' is not a hexidecimal digit [0-9A-Fa-f], -1 is returned.
 * otherwise the converted value is returned.
 */
static int
hexchar2int(char c)
{
	if (c >= '0' && c <= '9') {
		return (c - '0');
	}
	if (c >= 'A' && c <= 'F') {
		return (c - 'A' + 10);
	}
	if (c >= 'a' && c <= 'f') {
		return (c - 'a' + 10);
	}
	return (-1);
}

static int
adj_substring_filter(char *val)
{
	char		*nextstar;

	for (; val != NULL; val = nextstar) {
		if ((nextstar = find_star(val)) != NULL) {
			*nextstar++ = '\0';
		}

		if (*val != '\0') {
			if (unescape_filterval(val) < 0) {
				return (-1);
			}
		}
	}

	return (0);
}

/* ***** End of modified libldap.so.5 filter parser ***** */


/*
 * Walk filter, remove redundant parentheses in-line
 * verify that the filter is reasonable
 */
static int
validate_filter(ns_ldap_cookie_t *cookie)
{
	char			*filter = cookie->filter;
	int			rc;

	/* Parse filter looking for illegal values */

	rc = adj_filter(filter);
	if (rc != 0) {
		return (NS_LDAP_OP_FAILED);
	}

	/* end of filter checking */

	return (NS_LDAP_SUCCESS);
}

/*
 * Set the account management request control that needs to be sent to server.
 * This control is required to get the account management information of
 * a user to do local account checking.
 */
static int
setup_acctmgmt_params(ns_ldap_cookie_t *cookie)
{
	LDAPControl	*req, **requestctrls;

	req = calloc(1, sizeof (LDAPControl));

	if (req == NULL)
		return (NS_LDAP_MEMORY);

	/* fill in the fields of this new control */
	req->ldctl_iscritical = 1;
	req->ldctl_oid = strdup(NS_LDAP_ACCOUNT_USABLE_CONTROL);
	if (req->ldctl_oid == NULL) {
		free(req);
		return (NS_LDAP_MEMORY);
	}

	requestctrls = (LDAPControl **)calloc(2, sizeof (LDAPControl *));
	if (requestctrls == NULL) {
		ldap_control_free(req);
		return (NS_LDAP_MEMORY);
	}

	requestctrls[0] = req;

	cookie->p_serverctrls = requestctrls;

	return (NS_LDAP_SUCCESS);
}

/*
 * int get_new_acct_more_info(BerElement *ber,
 *     AcctUsableResponse_t *acctResp)
 *
 * Decode the more_info data from an Account Management control response,
 * when the account is not usable and when code style is from recent LDAP
 * servers (see below comments for parse_acct_cont_resp_msg() to get more
 * details on coding styles and ASN1 description).
 *
 * Expected BER encoding: {tbtbtbtiti}
 *      +t: tag is 0
 *	+b: TRUE if inactive due to account inactivation
 *      +t: tag is 1
 *	+b: TRUE if password has been reset
 *      +t: tag is 2
 *	+b: TRUE if password is expired
 *	+t: tag is 3
 *	+i: contains num of remaining grace, 0 means no grace
 *	+t: tag is 4
 *	+i: contains num of seconds before auto-unlock. -1 means acct is locked
 *		forever (i.e. until reset)
 *
 * Asumptions:
 * - ber is not null
 * - acctResp is not null and is initialized with default values for the
 *   fields in its AcctUsableResp.more_info structure
 * - the ber stream is received in the correct order, per the ASN1 description.
 *   We do not check this order and make the asumption that it is correct.
 *   Note that the ber stream may not (and will not in most cases) contain
 *   all fields.
 */
static int
get_new_acct_more_info(BerElement *ber, AcctUsableResponse_t *acctResp)
{
	int		rc = NS_LDAP_SUCCESS;
	char		errstr[MAXERROR];
	ber_tag_t	rTag = LBER_DEFAULT;
	ber_len_t	rLen = 0;
	ber_int_t	rValue;
	char		*last;
	int		berRC = 0;

	/*
	 * Look at what more_info BER element is/are left to be decoded.
	 * look at each of them 1 by 1, without checking on their order
	 * and possible multi values.
	 */
	for (rTag = ber_first_element(ber, &rLen, &last);
	    rTag != LBER_END_OF_SEQORSET;
	    rTag = ber_next_element(ber, &rLen, last)) {

		berRC = 0;
		switch (rTag) {
		case 0 | LBER_CLASS_CONTEXT | LBER_PRIMITIVE:
			/* inactive */
			berRC = ber_scanf(ber, "b", &rValue);
			if (berRC != LBER_ERROR) {
				(acctResp->AcctUsableResp).more_info.
				    inactive = (rValue != 0) ? 1 : 0;
			}
			break;

		case 1 | LBER_CLASS_CONTEXT | LBER_PRIMITIVE:
			/* reset */
			berRC = ber_scanf(ber, "b", &rValue);
			if (berRC != LBER_ERROR) {
				(acctResp->AcctUsableResp).more_info.reset
				    = (rValue != 0) ? 1 : 0;
			}
			break;

		case 2 | LBER_CLASS_CONTEXT | LBER_PRIMITIVE:
			/* expired */
			berRC = ber_scanf(ber, "b", &rValue);
			if (berRC != LBER_ERROR) {
				(acctResp->AcctUsableResp).more_info.expired
				    = (rValue != 0) ? 1 : 0;
			}
			break;

		case 3 | LBER_CLASS_CONTEXT | LBER_PRIMITIVE:
			/* remaining grace */
			berRC = ber_scanf(ber, "i", &rValue);
			if (berRC != LBER_ERROR) {
				(acctResp->AcctUsableResp).more_info.rem_grace
				    = rValue;
			}
			break;

		case 4 | LBER_CLASS_CONTEXT | LBER_PRIMITIVE:
			/* seconds before unlock */
			berRC = ber_scanf(ber, "i", &rValue);
			if (berRC != LBER_ERROR) {
				(acctResp->AcctUsableResp).more_info.
				    sec_b4_unlock = rValue;
			}
			break;

		default :
			(void) sprintf(errstr,
			    gettext("invalid reason tag 0x%x"), rTag);
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			rc = NS_LDAP_INTERNAL;
			break;
		}
		if (berRC == LBER_ERROR) {
			(void) sprintf(errstr,
			    gettext("error 0x%x decoding value for "
			    "tag 0x%x"), berRC, rTag);
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			rc = NS_LDAP_INTERNAL;
		}
		if (rc != NS_LDAP_SUCCESS) {
			/* exit the for loop */
			break;
		}
	}

	return (rc);
}

/*
 * int get_old_acct_opt_more_info(BerElement *ber,
 *     AcctUsableResponse_t *acctResp)
 *
 * Decode the optional more_info data from an Account Management control
 * response, when the account is not usable and when code style is from LDAP
 * server 5.2p4 (see below comments for parse_acct_cont_resp_msg() to get more
 * details on coding styles and ASN1 description).
 *
 * Expected BER encoding: titi}
 *	+t: tag is 2
 *	+i: contains num of remaining grace, 0 means no grace
 *	+t: tag is 3
 *	+i: contains num of seconds before auto-unlock. -1 means acct is locked
 *		forever (i.e. until reset)
 *
 * Asumptions:
 * - ber is a valid BER element
 * - acctResp is initialized for the fields in its AcctUsableResp.more_info
 *   structure
 */
static int
get_old_acct_opt_more_info(ber_tag_t tag, BerElement *ber,
    AcctUsableResponse_t *acctResp)
{
	int		rc = NS_LDAP_SUCCESS;
	char		errstr[MAXERROR];
	ber_len_t	len;
	int		rem_grace, sec_b4_unlock;

	switch (tag) {
	case 2:
		/* decode and maybe 3 is following */
		if ((tag = ber_scanf(ber, "i", &rem_grace)) == LBER_ERROR) {
			(void) sprintf(errstr, gettext("Can not get "
			    "rem_grace"));
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			rc = NS_LDAP_INTERNAL;
			break;
		}
		(acctResp->AcctUsableResp).more_info.rem_grace = rem_grace;

		if ((tag = ber_peek_tag(ber, &len)) == LBER_ERROR) {
			/* this is a success case, break to exit */
			(void) sprintf(errstr, gettext("No more "
			    "optional data"));
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			break;
		}

		if (tag == 3) {
			if (ber_scanf(ber, "i", &sec_b4_unlock) == LBER_ERROR) {
				(void) sprintf(errstr,
				    gettext("Can not get sec_b4_unlock "
				    "- 1st case"));
				syslog(LOG_DEBUG, "libsldap: %s", errstr);
				rc = NS_LDAP_INTERNAL;
				break;
			}
			(acctResp->AcctUsableResp).more_info.sec_b4_unlock =
			    sec_b4_unlock;
		} else { /* unknown tag */
			(void) sprintf(errstr, gettext("Unknown tag "
			    "- 1st case"));
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			rc = NS_LDAP_INTERNAL;
			break;
		}
		break;

	case 3:
		if (ber_scanf(ber, "i", &sec_b4_unlock) == LBER_ERROR) {
			(void) sprintf(errstr, gettext("Can not get "
			    "sec_b4_unlock - 2nd case"));
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			rc = NS_LDAP_INTERNAL;
			break;
		}
		(acctResp->AcctUsableResp).more_info.sec_b4_unlock =
		    sec_b4_unlock;
		break;

	default: /* unknown tag */
		(void) sprintf(errstr, gettext("Unknown tag - 2nd case"));
		syslog(LOG_DEBUG, "libsldap: %s", errstr);
		rc = NS_LDAP_INTERNAL;
		break;
	}

	return (rc);
}

/*
 * **** This function needs to be moved to libldap library ****
 * parse_acct_cont_resp_msg() parses the message received by server according to
 * following format (ASN1 notation):
 *
 *	ACCOUNT_USABLE_RESPONSE::= CHOICE {
 *		is_available		[0] INTEGER,
 *				** seconds before expiration **
 *		is_not_available	[1] more_info
 *	}
 *	more_info::= SEQUENCE {
 *		inactive		[0] BOOLEAN DEFAULT FALSE,
 *		reset			[1] BOOLEAN DEFAULT FALSE,
 *		expired			[2] BOOLEAN DEFAULT FALSE,
 *		remaining_grace		[3] INTEGER OPTIONAL,
 *		seconds_before_unlock	[4] INTEGER OPTIONAL
 *	}
 */
/*
 * #define used to make the difference between coding style as done
 * by LDAP server 5.2p4 and newer LDAP servers. There are 4 values:
 * - DS52p4_USABLE: 5.2p4 coding style, account is usable
 * - DS52p4_NOT_USABLE: 5.2p4 coding style, account is not usable
 * - NEW_USABLE: newer LDAP servers coding style, account is usable
 * - NEW_NOT_USABLE: newer LDAP servers coding style, account is not usable
 *
 * An account would be considered not usable if for instance:
 * - it's been made inactive in the LDAP server
 * - or its password was reset in the LDAP server database
 * - or its password expired
 * - or the account has been locked, possibly forever
 */
#define	DS52p4_USABLE		0x00
#define	DS52p4_NOT_USABLE	0x01
#define	NEW_USABLE		0x00 | LBER_CLASS_CONTEXT | LBER_PRIMITIVE
#define	NEW_NOT_USABLE		0x01 | LBER_CLASS_CONTEXT | LBER_CONSTRUCTED
static int
parse_acct_cont_resp_msg(LDAPControl **ectrls, AcctUsableResponse_t *acctResp)
{
	int		rc = NS_LDAP_SUCCESS;
	BerElement	*ber;
	ber_tag_t	tag;
	ber_len_t	len;
	int		i;
	char		errstr[MAXERROR];
	/* used for any coding style when account is usable */
	int		seconds_before_expiry;
	/* used for 5.2p4 coding style when account is not usable */
	int		inactive, reset, expired;

	if (ectrls == NULL) {
		(void) sprintf(errstr, gettext("Invalid ectrls parameter"));
		syslog(LOG_DEBUG, "libsldap: %s", errstr);
		return (NS_LDAP_INVALID_PARAM);
	}

	for (i = 0; ectrls[i] != NULL; i++) {
		if (strcmp(ectrls[i]->ldctl_oid, NS_LDAP_ACCOUNT_USABLE_CONTROL)
		    == 0) {
			break;
		}
	}

	if (ectrls[i] == NULL) {
		/* Ldap control is not found */
		(void) sprintf(errstr, gettext("Account Usable Control "
		    "not found"));
		syslog(LOG_DEBUG, "libsldap: %s", errstr);
		return (NS_LDAP_NOTFOUND);
	}

	/* Allocate a BER element from the control value and parse it. */
	if ((ber = ber_init(&ectrls[i]->ldctl_value)) == NULL)
		return (NS_LDAP_MEMORY);

	if ((tag = ber_peek_tag(ber, &len)) == LBER_ERROR) {
		/* Ldap decoding error */
		(void) sprintf(errstr, gettext("Error decoding 1st tag"));
		syslog(LOG_DEBUG, "libsldap: %s", errstr);
		ber_free(ber, 1);
		return (NS_LDAP_INTERNAL);
	}

	switch (tag) {
	case DS52p4_USABLE:
	case NEW_USABLE:
		acctResp->choice = 0;
		if (ber_scanf(ber, "i", &seconds_before_expiry)
		    == LBER_ERROR) {
			/* Ldap decoding error */
			(void) sprintf(errstr, gettext("Can not get "
			    "seconds_before_expiry"));
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			rc = NS_LDAP_INTERNAL;
			break;
		}
		/* ber_scanf() succeeded */
		(acctResp->AcctUsableResp).seconds_before_expiry =
		    seconds_before_expiry;
		break;

	case DS52p4_NOT_USABLE:
		acctResp->choice = 1;
		if (ber_scanf(ber, "{bbb", &inactive, &reset, &expired)
		    == LBER_ERROR) {
			/* Ldap decoding error */
			(void) sprintf(errstr, gettext("Can not get "
			    "inactive/reset/expired"));
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			rc = NS_LDAP_INTERNAL;
			break;
		}
		/* ber_scanf() succeeded */
		(acctResp->AcctUsableResp).more_info.inactive =
		    ((inactive == 0) ? 0 : 1);
		(acctResp->AcctUsableResp).more_info.reset =
		    ((reset == 0) ? 0 : 1);
		(acctResp->AcctUsableResp).more_info.expired =
		    ((expired == 0) ? 0 : 1);
		(acctResp->AcctUsableResp).more_info.rem_grace = 0;
		(acctResp->AcctUsableResp).more_info.sec_b4_unlock = 0;

		if ((tag = ber_peek_tag(ber, &len)) == LBER_ERROR) {
			/* this is a success case, break to exit */
			(void) sprintf(errstr, gettext("No optional data"));
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			break;
		}

		/*
		 * Look at what optional more_info BER element is/are
		 * left to be decoded.
		 */
		rc = get_old_acct_opt_more_info(tag, ber, acctResp);
		break;

	case NEW_NOT_USABLE:
		acctResp->choice = 1;
		/*
		 * Recent LDAP servers won't code more_info data for default
		 * values (see above comments on ASN1 description for what
		 * fields have default values & what fields are optional).
		 */
		(acctResp->AcctUsableResp).more_info.inactive = 0;
		(acctResp->AcctUsableResp).more_info.reset = 0;
		(acctResp->AcctUsableResp).more_info.expired = 0;
		(acctResp->AcctUsableResp).more_info.rem_grace = 0;
		(acctResp->AcctUsableResp).more_info.sec_b4_unlock = 0;

		if (len == 0) {
			/*
			 * Nothing else to decode; this is valid and we
			 * use default values set above.
			 */
			(void) sprintf(errstr, gettext("more_info is "
			    "empty, using default values"));
			syslog(LOG_DEBUG, "libsldap: %s", errstr);
			break;
		}

		/*
		 * Look at what more_info BER element is/are left to
		 * be decoded.
		 */
		rc = get_new_acct_more_info(ber, acctResp);
		break;

	default:
		(void) sprintf(errstr, gettext("unknwon coding style "
		    "(tag: 0x%x)"), tag);
		syslog(LOG_DEBUG, "libsldap: %s", errstr);
		rc = NS_LDAP_INTERNAL;
		break;
	}

	ber_free(ber, 1);
	return (rc);
}

/*
 * internal function for __ns_ldap_getAcctMgmt()
 */
static int
getAcctMgmt(const char *user, AcctUsableResponse_t *acctResp,
    ns_conn_user_t *conn_user)
{
	int		scope, rc;
	ns_ldap_cookie_t	*cookie;
	ns_ldap_search_desc_t	**sdlist = NULL;
	ns_ldap_search_desc_t	*dptr;
	ns_ldap_error_t		*error = NULL;
	char			**dns = NULL;
	char		service[] = "shadow";

	if (user == NULL || acctResp == NULL)
		return (NS_LDAP_INVALID_PARAM);

	/* Initialize State machine cookie */
	cookie = init_search_state_machine();
	if (cookie == NULL)
		return (NS_LDAP_MEMORY);
	cookie->conn_user = conn_user;

	/* see if need to follow referrals */
	rc = __s_api_toFollowReferrals(0,
	    &cookie->followRef, &error);
	if (rc != NS_LDAP_SUCCESS) {
		(void) __ns_ldap_freeError(&error);
		goto out;
	}

	/* get the service descriptor - or create a default one */
	rc = __s_api_get_SSD_from_SSDtoUse_service(service,
	    &sdlist, &error);
	if (rc != NS_LDAP_SUCCESS) {
		(void) __ns_ldap_freeError(&error);
		goto out;
	}

	if (sdlist == NULL) {
		/* Create default service Desc */
		sdlist = (ns_ldap_search_desc_t **)calloc(2,
		    sizeof (ns_ldap_search_desc_t *));
		if (sdlist == NULL) {
			rc = NS_LDAP_MEMORY;
			goto out;
		}
		dptr = (ns_ldap_search_desc_t *)
		    calloc(1, sizeof (ns_ldap_search_desc_t));
		if (dptr == NULL) {
			free(sdlist);
			rc = NS_LDAP_MEMORY;
			goto out;
		}
		sdlist[0] = dptr;

		/* default base */
		rc = __s_api_getDNs(&dns, service, &cookie->errorp);
		if (rc != NS_LDAP_SUCCESS) {
			if (dns) {
				__s_api_free2dArray(dns);
				dns = NULL;
			}
			(void) __ns_ldap_freeError(&(cookie->errorp));
			cookie->errorp = NULL;
			goto out;
		}
		dptr->basedn = strdup(dns[0]);
		if (dptr->basedn == NULL) {
			free(sdlist);
			free(dptr);
			if (dns) {
				__s_api_free2dArray(dns);
				dns = NULL;
			}
			rc = NS_LDAP_MEMORY;
			goto out;
		}
		__s_api_free2dArray(dns);
		dns = NULL;

		/* default scope */
		scope = 0;
		rc = __s_api_getSearchScope(&scope, &cookie->errorp);
		dptr->scope = scope;
	}

	cookie->sdlist = sdlist;

	cookie->service = strdup(service);
	if (cookie->service == NULL) {
		rc = NS_LDAP_MEMORY;
		goto out;
	}

	/* search for entries for this particular uid */
	(void) asprintf(&cookie->i_filter, "(uid=%s)", user);
	if (cookie->i_filter == NULL) {
		rc = NS_LDAP_MEMORY;
		goto out;
	}

	/* create the control request */
	if ((rc = setup_acctmgmt_params(cookie)) != NS_LDAP_SUCCESS)
		goto out;

	/* Process search */
	rc = search_state_machine(cookie, GET_ACCT_MGMT_INFO, 0);

	/* Copy results back to user */
	rc = cookie->err_rc;
	if (rc != NS_LDAP_SUCCESS)
			(void) __ns_ldap_freeError(&(cookie->errorp));

	if (cookie->result == NULL)
			goto out;

	if ((rc = parse_acct_cont_resp_msg(cookie->resultctrl, acctResp))
	    != NS_LDAP_SUCCESS)
		goto out;

	rc = NS_LDAP_SUCCESS;

out:
	delete_search_cookie(cookie);

	return (rc);
}

/*
 * __ns_ldap_getAcctMgmt() is called from pam account management stack
 * for retrieving accounting information of users with no user password -
 * eg. rlogin, rsh, etc. This function uses the account management control
 * request to do a search on the server for the user in question. The
 * response control returned from the server is got from the cookie.
 * Input params: username of whose account mgmt information is to be got
 *		 pointer to hold the parsed account management information
 * Return values: NS_LDAP_SUCCESS on success or appropriate error
 *		code on failure
 */
int
__ns_ldap_getAcctMgmt(const char *user, AcctUsableResponse_t *acctResp)
{
	ns_conn_user_t	*cu = NULL;
	int		try_cnt = 0;
	int		rc = NS_LDAP_SUCCESS;
	ns_ldap_error_t	*error = NULL;

	for (;;) {
		if (__s_api_setup_retry_search(&cu, NS_CONN_USER_SEARCH,
		    &try_cnt, &rc, &error) == 0)
			break;
		rc = getAcctMgmt(user, acctResp, cu);
	}
	return (rc);
}
